<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Lickability Blog</title><description>The latest from Lickability</description><link>https://lickability.com/</link><item><title>Animating Strikethroughs in SwiftUI</title><link>https://lickability.com/blog/animating-strikethroughs-in-swiftui/</link><guid isPermaLink="true">https://lickability.com/blog/animating-strikethroughs-in-swiftui/</guid><description>Solving problems the right way.</description><pubDate>Wed, 25 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While working on one of our internal tools, the design called for completed tasks in todo lists to be “struck through,” adding a bit of playfulness and physicality to the app. SwiftUI does provide a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;strikethrough&lt;/code&gt; modifier, but it is not animatable. For our tool, we wanted it to feel like it was being &lt;em&gt;physically drawn&lt;/em&gt;, stroked from one end of the text to the other. What seemed like a simple feature turned out to be deceptively difficult if I wanted to implement it the &lt;em&gt;right&lt;/em&gt; way.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Default implementation of strikethroughs in Swift vs. the intended effect. Animated and non animated examples.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/27f191be193555e992369274c6c2ca01290a9482-645x376.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=323 323w, https://cdn.sanity.io/images/nkt6o869/production/27f191be193555e992369274c6c2ca01290a9482-645x376.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=645 645w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/27f191be193555e992369274c6c2ca01290a9482-645x376.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=645&quot; width=&quot;645&quot; height=&quot;376&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Default implementation of strikethroughs in Swift vs. the intended effect.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;&lt;/p&gt;&lt;h3&gt;Defining constraints to identify a solution&lt;/h3&gt;&lt;p&gt;Before immediately researching a solution, I defined three constraints my approach would need to satisfy:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;The stroke needed to span the entire view if the text grew or shrank with Dynamic Type.&lt;/li&gt;&lt;li&gt;The stroke had to span the entire text view, even when text wrapped to multiple lines.&lt;/li&gt;&lt;li&gt;Existing VoiceOver behavior had to be preserved.&lt;/li&gt;&lt;/ol&gt;&lt;h4&gt;First round of experiments&lt;/h4&gt;&lt;p&gt;My first attempt used a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Path&lt;/code&gt; as an overlay on the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; view. The drawback was that it didn’t account for multiline text or Dynamic Type, because I had no access to precise width information about the rendered text.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Path as an overlay on the Text view’s issues with multiline text.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/0b8f4654e0213974a5531cf151c1c63121cd549a-800x624.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/0b8f4654e0213974a5531cf151c1c63121cd549a-800x624.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/0b8f4654e0213974a5531cf151c1c63121cd549a-800x624.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/0b8f4654e0213974a5531cf151c1c63121cd549a-800x624.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/0b8f4654e0213974a5531cf151c1c63121cd549a-800x624.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800&quot; width=&quot;800&quot; height=&quot;624&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Path&lt;/code&gt; as an overlay on the &lt;code index=&quot;2&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; view’s issues with multiline text.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;&lt;/p&gt;&lt;p&gt;Next, I tried a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Canvas&lt;/code&gt; view overlay. This felt promising — a graphics context meant I could draw freely — but I still lacked precise information about the rendered text, such as line height, padding, and font size. It worked for single-line text but broke entirely when the text wrapped to multiple lines.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Canvas view overlay presented problems with animation and, still, multi-line text.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/88cbc54f2a1d3b732a8238e6579a3776c24fdb94-1280x933.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=320 320w, https://cdn.sanity.io/images/nkt6o869/production/88cbc54f2a1d3b732a8238e6579a3776c24fdb94-1280x933.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=640 640w, https://cdn.sanity.io/images/nkt6o869/production/88cbc54f2a1d3b732a8238e6579a3776c24fdb94-1280x933.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=960 960w, https://cdn.sanity.io/images/nkt6o869/production/88cbc54f2a1d3b732a8238e6579a3776c24fdb94-1280x933.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1280 1280w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/88cbc54f2a1d3b732a8238e6579a3776c24fdb94-1280x933.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1280&quot; width=&quot;1280&quot; height=&quot;933&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Canvas&lt;/code&gt; view overlay presented problems with animation and, still, multi-line text.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;&lt;/p&gt;&lt;p&gt;My search led me to &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Text.Layout&lt;/code&gt;, which provided access to the internal structure of a rendered &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; view, broken down into lines, runs, and individual glyphs, as well as iOS 17’s &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Text​Renderer&lt;/code&gt; protocol, which surfaces both the layout and a graphics context through a single required method:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; draw&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: Text.Layout, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; ctx&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;inout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; GraphicsContext)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;&lt;/p&gt;&lt;p&gt;The implementation iterates over each line, calculates how much of the strikethrough stroke should be visible based on a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;_progress&lt;/code&gt; value between &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;0&lt;/code&gt; and &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;1&lt;/code&gt;, and draws accordingly. Progress is tracked cumulatively across lines so the stroke draws continuously from one line into the next rather than animating each line independently:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; draw&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: Text.Layout, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; ctx&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;inout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; GraphicsContext) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; totalLength = layout.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;reduce&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) { &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;$0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;$1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;typographicBounds&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; accumulated: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; line &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; layout {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; bounds = line.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;typographicBounds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; strikeWidth = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;min&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;max&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(totalLength * _progress - accumulated, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), bounds.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;         &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        ctx.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;draw&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(line)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; strikeWidth &gt; &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; midY = bounds.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;midY&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; startX = bounds.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;minX&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            ctx.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;stroke&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;                Path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { path &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    path.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;move&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CGPoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: startX, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;y&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: midY))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    path.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;addLine&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CGPoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: startX + strikeWidth, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;y&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: midY))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;                with&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;color&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(color),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;                lineWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: lineWidth&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        accumulated += bounds.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;width&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;&lt;/p&gt;&lt;p&gt;Conforming to &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Animatable&lt;/code&gt; is what makes SwiftUI interpolate the renderer between states rather than jumping straight to the final value:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; animatableData: CGFloat {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    get&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { _progress }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { _progress = newValue }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;&lt;/p&gt;&lt;p&gt;Once applied via the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;text​Renderer&lt;/code&gt; modifier, the strikethrough animated perfectly on &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; views. Then I tried it on a &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Text​Field&lt;/code&gt;, and nothing rendered. Setting a breakpoint confirmed &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;draw&lt;/code&gt; wasn’t even being called. &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;Text​Field&lt;/code&gt; doesn’t render its content as &lt;code index=&quot;11&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; views internally.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;h4&gt;Disappointing, but not disqualifying&lt;/h4&gt;&lt;p&gt;The &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Text​Renderer&lt;/code&gt; approach still satisfied all three constraints for &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; views. I just needed to get creative about how to apply it to a &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Text​Field&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;What if I overlaid a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; view on top of the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Text​Field&lt;/code&gt;? The &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; view would display the animated strikethrough, and I could set its foreground color to clear, keeping the &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;Text​Field&lt;/code&gt; content visible underneath while the animation played on top.&lt;/p&gt;&lt;p&gt;This worked well in initial testing, but when I integrated it into the project, a subtle misalignment appeared: &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Text​Field&lt;/code&gt; tends to fit more content on a single line, while &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; wraps earlier. This meant the first line of the &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Text​Field&lt;/code&gt; had no strikethrough, while the second line did.&lt;/p&gt;&lt;p&gt;To fix the alignment, I created a custom &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Layout&lt;/code&gt; that forces both views to use the same width. This ensures both the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Text​Field&lt;/code&gt; and the &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; view wrap at the same point, keeping the strikethrough in sync with the visible content. The tradeoff is a slightly narrower &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;Text​Field&lt;/code&gt;, but that was acceptable for our use case.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; TextFieldOverlayLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Layout &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; sizeThatFits&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;proposal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: ProposedViewSize, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subviews&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: Subviews, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cache&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;inout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ()) -&gt; CGSize {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        guard&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; primary = subviews.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;first&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;zero&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; primary.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sizeThatFits&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(proposal).width.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isZero&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; secondIndex = subviews.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;after&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; subviews[secondIndex].&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sizeThatFits&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(proposal)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        } &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; primary.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sizeThatFits&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ProposedViewSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: proposal.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; placeSubviews&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; bounds&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: CGRect, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;proposal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: ProposedViewSize, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subviews&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: Subviews, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cache&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;inout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ()) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        guard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; subviews.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &gt;= &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; subview &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; subviews {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            subview.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;place&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;at&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: bounds.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;origin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;proposal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ProposedViewSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(bounds.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;&lt;/p&gt;&lt;p&gt;In the view, the layout looked like this:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;TextFieldOverlayLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(store.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;       .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;foregroundStyle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(Color.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;clear&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;font&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;system&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;design&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rounded&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;weight&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;medium&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;lineSpacing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;-1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;allowsHitTesting&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;accessibilityHidden&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;textRenderer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;StrikethroughRenderer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(store.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isComplete&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;color&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;primary&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                       &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;   Textfield&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;       .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;foregroundStyle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(Color.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;primary&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;       .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;font&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;system&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;design&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rounded&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;weight&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;medium&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;       .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;lineSpacing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;-1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;&lt;/p&gt;&lt;h4&gt;The end result&lt;/h4&gt;&lt;p&gt;My final draft required research, a few dead ends, and ultimately combining &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Text​Renderer&lt;/code&gt;, a clear &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; overlay, and a custom &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Layout&lt;/code&gt; to produce something that satisfied my aforementioned criteria. It may seem like a lot of effort for a small detail, but I believe these are exactly the kind of details worth putting extra time into! The way it feels to use an app is just as important as the functionality of the app itself.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Animated examples of the functional strikethrough renderer. Single line and Multi line examples.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/10dcf0927bc7afad3ab37eaaee2e84214c299fb3-800x636.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/10dcf0927bc7afad3ab37eaaee2e84214c299fb3-800x636.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/10dcf0927bc7afad3ab37eaaee2e84214c299fb3-800x636.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/10dcf0927bc7afad3ab37eaaee2e84214c299fb3-800x636.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/10dcf0927bc7afad3ab37eaaee2e84214c299fb3-800x636.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800&quot; width=&quot;800&quot; height=&quot;636&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;The final working result.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;If you’re interested in working with a team who cares as much about the fine details as you do, &lt;a href=&quot;https://lickability.com/services/&quot;&gt;reach out&lt;/a&gt;! We’re always ready to help you build. 😜&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Ashli Rankin</author></item><item><title>Our Work with RevenueCat</title><link>https://lickability.com/blog/our-work-with-revenuecat/</link><guid isPermaLink="true">https://lickability.com/blog/our-work-with-revenuecat/</guid><description>A brief history of a likely partnership.</description><pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As an independent studio of over 10 years, we’ve always been advocates for tools that empower small teams and solo developers. Enter &lt;a href=&quot;https://revenuecat.com&quot;&gt;RevenueCat:&lt;/a&gt; a company with a suite of tools for app developers that handles subscription management, billing, detailed analytics, and more—a match made in heaven for us! We joined RevenueCat as a premier partner in 2023 as a user of their tools, and to implement some of their SDKs into our own projects, so it was an honor when they came to us to develop their first official iOS app.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Screenshot of RevenueCat for iOS&amp;#x27;s interactive charts feature.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/57697ed0dd864b84899575fa98b829ca3465c15a-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=323 323w, https://cdn.sanity.io/images/nkt6o869/production/57697ed0dd864b84899575fa98b829ca3465c15a-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=645 645w, https://cdn.sanity.io/images/nkt6o869/production/57697ed0dd864b84899575fa98b829ca3465c15a-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=968 968w, https://cdn.sanity.io/images/nkt6o869/production/57697ed0dd864b84899575fa98b829ca3465c15a-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1290 1290w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/57697ed0dd864b84899575fa98b829ca3465c15a-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1290&quot; width=&quot;1290&quot; height=&quot;2796&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;&lt;/p&gt;&lt;p&gt;RevenueCat’s initial problem was obvious: they were a large, growing startup that empowered app developers by simplifying financials, but at the time had no official app themselves.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;“They were laser focused on building better features and tools while doing developer outreach. They knew our track record, so it was an exciting opportunity for us to work with another client whose outlook and &lt;a href=&quot;https://x.com/ThomasDeVuono/status/1906699754005794919&quot;&gt;attention to detail&lt;/a&gt; gelled with ours. They could focus their internal efforts on bettering things for their users, and we’d implement those features down the road.”&lt;br&gt;&lt;br&gt;– mb (Partner)&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Screenshot of RevenueCat for iOS&amp;#x27;s customer history feature.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b6ced16447e4b8a0ef85f5b6a66dfccb184a37ec-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=323 323w, https://cdn.sanity.io/images/nkt6o869/production/b6ced16447e4b8a0ef85f5b6a66dfccb184a37ec-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=645 645w, https://cdn.sanity.io/images/nkt6o869/production/b6ced16447e4b8a0ef85f5b6a66dfccb184a37ec-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=968 968w, https://cdn.sanity.io/images/nkt6o869/production/b6ced16447e4b8a0ef85f5b6a66dfccb184a37ec-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1290 1290w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b6ced16447e4b8a0ef85f5b6a66dfccb184a37ec-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1290&quot; width=&quot;1290&quot; height=&quot;2796&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;&lt;/p&gt;&lt;p&gt;So now it’s time to build... but what does it look like to “officially translate into iOS”? Where do we draw inspiration?&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;“We’d been lucky enough to attend WWDC in Cupertino where we took part in a design lab put on by Apple. We wanted the app to be 100% at-home in and take full advantage of what iOS had to offer: widgets, universal links, and Swift Charts which had been newly introduced to SwiftUI. &lt;a href=&quot;https://samhenri.gold&quot;&gt;Sam&lt;/a&gt; did an excellent job designing the app, and special shout-outs should be made to friend of the team &lt;a href=&quot;https://joecieplinski.com/&quot;&gt;Joe&lt;/a&gt; for working with Brian on implementation.”&lt;br&gt;&lt;br&gt;– mb&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Screenshot of RevenueCat chart and metric widgets for iPadOS.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/30c25a64ccb70f44acbcf0f846693f66377efc61-2752x2064.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/30c25a64ccb70f44acbcf0f846693f66377efc61-2752x2064.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/30c25a64ccb70f44acbcf0f846693f66377efc61-2752x2064.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/30c25a64ccb70f44acbcf0f846693f66377efc61-2752x2064.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/30c25a64ccb70f44acbcf0f846693f66377efc61-2752x2064.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/30c25a64ccb70f44acbcf0f846693f66377efc61-2752x2064.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/30c25a64ccb70f44acbcf0f846693f66377efc61-2752x2064.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2752 2752w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/30c25a64ccb70f44acbcf0f846693f66377efc61-2752x2064.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;&lt;/p&gt;&lt;p&gt;And in the ensuing years, &lt;a href=&quot;https://apple.co/4guqAfm&quot;&gt;we shipped an official iOS app,&lt;/a&gt; worked closely with RevenueCat on implementing brand-new features, and got to attend several &lt;a href=&quot;https://x.com/lickability/status/1970563274216226883&quot;&gt;awesome&lt;/a&gt; &lt;a href=&quot;https://x.com/lickability/status/1939767547257106874&quot;&gt;events&lt;/a&gt; put on by them. In 2025 alone we pushed &lt;em&gt;6&lt;/em&gt; significant version updates, and we collected some of our favorite features here:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;List of RevenueCat version features released in 2025 from v1.2 to v1.8.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/ac0566f6d3e4e79ab58d50f349dc2dcc269a2b47-1600x2100.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/ac0566f6d3e4e79ab58d50f349dc2dcc269a2b47-1600x2100.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/ac0566f6d3e4e79ab58d50f349dc2dcc269a2b47-1600x2100.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/ac0566f6d3e4e79ab58d50f349dc2dcc269a2b47-1600x2100.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/ac0566f6d3e4e79ab58d50f349dc2dcc269a2b47-1600x2100.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;2100&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;We’re super excited for what the future will bring out of this partnership, and we have so much more we’re not ready to talk about yet 👀&lt;br&gt;&lt;br&gt;…but if a project of this or any size interests you, feel free to &lt;a href=&quot;https://lickability.com/services/&quot;&gt;reach out&lt;/a&gt; in the meantime!&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Team Lickability</author></item><item><title>Too Much of a Good Thing: Scope and the Future of Apps</title><link>https://lickability.com/blog/too-much-of-a-good-thing/</link><guid isPermaLink="true">https://lickability.com/blog/too-much-of-a-good-thing/</guid><description>Now that we can do anything, why are we trying to do everything?</description><pubDate>Tue, 10 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few years ago, Lickability alumnus &lt;a href=&quot;https://samhenri.gold&quot;&gt;Sam Henri Gold&lt;/a&gt; made a strong case for &lt;a href=&quot;https://lickability.com/blog/your-app-doesnt-need-a-chat-feature/&quot;&gt;why your app doesn‘t need a chat feature.&lt;/a&gt; So much has changed in this short time; in the ensuing years, most tech sectors have seen huge financial swings, &lt;a href=&quot;https://www.reuters.com/business/world-at-work/pinterest-cuts-nearly-15-jobs-redirect-resources-ai-2026-01-27/&quot;&gt;layoffs,&lt;/a&gt;  and product development shifts (often with &lt;a href=&quot;https://futurism.com/artificial-intelligence/microsoft-sell-ai-agents-disaster&quot;&gt;limited success&lt;/a&gt;), thanks largely to advancements and ballooning investments in AI. Public perception of AI still &lt;a href=&quot;https://www.pewresearch.org/internet/2025/04/03/how-the-us-public-and-ai-experts-view-artificial-intelligence/&quot;&gt;skews negative-neutral,&lt;/a&gt; but this hasn’t stopped many from feeling empowered to build due in part to AI putting no-barrier tools into the hands of talented, but less-technical, people. (See: &lt;a href=&quot;https://lovable.dev/&quot;&gt;Lovable,&lt;/a&gt; &lt;a href=&quot;https://claude.com/product/claude-code&quot;&gt;Claude Code,&lt;/a&gt; &lt;a href=&quot;https://github.com/stackblitz-labs/bolt.diy&quot;&gt;bolt.diy,&lt;/a&gt; etc.)&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;But even with this newfound promise that anyone can build anything, success is never guaranteed. What if you want to spin up an idea into a product you can sell to others? It’s easier now than ever, but also easier to stumble into the same — and &lt;em&gt;more&lt;/em&gt; — pitfalls of unnecessary features.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;This got us thinking: what do people even want out of apps in the frenetic landscape of 2026? What are product developers, designers, or even our prospective clients eager about adding to apps and software? Is everything becoming same-y “slop,” or is that an easy hand-wave?&lt;/p&gt;&lt;h3&gt;Incessant Features and Scope&lt;/h3&gt;&lt;p&gt;To learn what some of our prospective clients’ pitches have had in common, and how much they are (or aren’t) in touch with what the average user wants out of their app experience, we turned to our frontline team.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;With AI being so massively popular, everyone wants to tout some kind of integration with the hopes of riding this wave. But while these tools are nearly ubiquitous now, the minutiae involved in their adoption and integration still isn’t fully understood by the average person.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;I am seeing a lot of requests to “integrate AI chatbots” in the pitches we get, but people are not thinking any further on what model to use, how they intend to prompt it and the subsequent necessary pipelines, or even evaluating the drastically-differing costs to use said models. It’s fine to want to capitalize on new technologies, but people often aren’t considering what their end goals are when adding these features to their products.&lt;br&gt;&lt;br&gt;– Tom (Sr. Account Manager)&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Keeping scope in check continues to prove an issue for many. As mentioned before: AI has proven useful for technical and non-technical professionals to iterate and ideate very quickly, but can often lead to putting the cart before the horse and not knowing just how insecure, expensive, or deceptively-complicated proper integration will end up being.&lt;/p&gt;&lt;blockquote&gt;&lt;br&gt;Anything that brings in user-generated content. A great example is profile pictures / avatars. Clients want this but don’t know that this seemingly “easy feature” also necessitates bringing in moderation tools (manual or automatic) to secure against illegal or harmful content. This is an easy way to balloon scope but can sometimes be creatively worked around (e.g. user avatars as initials, pick from a pre-defined list of images, emoji etc.) &lt;br&gt;&lt;br&gt;– Tom&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;h4&gt;Creative workarounds for curbing scope creep&lt;/h4&gt;&lt;p&gt;A great example of giving users a sense of personal identity without needing to allocate time and money on content moderation is &lt;a href=&quot;https://transitapp.com/&quot;&gt;Transit‘s&lt;/a&gt; approach: randomly-assigned combinations of &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;adjective-emoji&lt;/code&gt; usernames with a matching avatar.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Randomly-generated and assigned Transit username Delulu Web with a cobweb emoji as the avatar.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/65bbe6daa96dc622aa5ddc98d9b366f5696baff4-1080x1004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=270 270w, https://cdn.sanity.io/images/nkt6o869/production/65bbe6daa96dc622aa5ddc98d9b366f5696baff4-1080x1004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=540 540w, https://cdn.sanity.io/images/nkt6o869/production/65bbe6daa96dc622aa5ddc98d9b366f5696baff4-1080x1004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=810 810w, https://cdn.sanity.io/images/nkt6o869/production/65bbe6daa96dc622aa5ddc98d9b366f5696baff4-1080x1004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1080 1080w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/65bbe6daa96dc622aa5ddc98d9b366f5696baff4-1080x1004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1080&quot; width=&quot;1080&quot; height=&quot;1004&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;&lt;/p&gt;&lt;p&gt;This is, however, also an example of &lt;em&gt;creativity thriving on restrictions&lt;/em&gt;, as it was once possible to pick your own custom username, with Transit removing the option in 2024 due to some users choosing hateful names and ruining the fun for everyone.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Screenshot from Transit App on the Appstore via the Wayback Machine showing a changelog for version 5.17.3 removing custom GO nicknames.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/fa964d69674b590a4bffe4aa6dba7fab8b22f49f-794x408.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=199 199w, https://cdn.sanity.io/images/nkt6o869/production/fa964d69674b590a4bffe4aa6dba7fab8b22f49f-794x408.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=397 397w, https://cdn.sanity.io/images/nkt6o869/production/fa964d69674b590a4bffe4aa6dba7fab8b22f49f-794x408.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=596 596w, https://cdn.sanity.io/images/nkt6o869/production/fa964d69674b590a4bffe4aa6dba7fab8b22f49f-794x408.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=794 794w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/fa964d69674b590a4bffe4aa6dba7fab8b22f49f-794x408.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=794&quot; width=&quot;794&quot; height=&quot;408&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;&lt;/p&gt;&lt;h3&gt;Building for Users&lt;/h3&gt;&lt;p&gt;Even if you keep scope in check and set out to create an app with the &lt;em&gt;intention&lt;/em&gt; of solving one problem, what &lt;em&gt;doesn’t&lt;/em&gt; it need? &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;‘We’re going to need a chat feature, and a micro-social network, and a feed for people you follow...’ In your &lt;em&gt;gardening&lt;/em&gt; app? People just want apps to solve problems or make things more convenient for them, especially lately. There’s already a proliferation of options for social media, not to mention the content moderation nightmare running your own could create. It may seem boring, but limiting your features and focusing on doing one smaller thing extremely well goes a long way for people.&lt;br&gt;&lt;br&gt;– mb (Partner)&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Realizing this problem with feature creep, we spoke with our internal operations expert for examples of software features devs and designers &lt;em&gt;swear&lt;/em&gt; she’ll use, but she finds herself not using:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;AI meeting notes are a big one for me, and it feels like they’re in everything now. I recognize that it can be helpful to have an extra backup since it can be hard to quickly take accurate notes while talking in a meeting, but I have never found it truly useful to me, and far from a complete replacement. I much prefer taking my own notes during meetings — it helps me stay focused, I have the context that an AI tool doesn‘t to determine what is important and what isn’t, and it’s often more valuable to pause and ask someone to repeat or clarify something they said than it is to look at notes that&lt;em&gt; I &lt;/em&gt;didn’t write and realize I don’t understand something.&lt;br&gt;&lt;br&gt;– Jillian (Project Manager)&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Does every app have to be an every&lt;em&gt;thing&lt;/em&gt; app?&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;I feel like so many apps try to be &lt;em&gt;everything&lt;/em&gt; and it tires me out. I’d rather have one really good calendar app than an app that has a calendar, a to-do list, a habit tracker, a focus timer, and an AI chatbot to coach you through it all. Just pick &lt;em&gt;one&lt;/em&gt; thing and do it well.&lt;br&gt;&lt;br&gt;– Jillian&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Software like &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian&lt;/a&gt; operates on a philosophy of “closed-core, open-source plugins.” At its core, Obsidian is very sparse, and its in-house development is focused on providing users with a program that’s efficient, secure, and available on a number of platforms. Is there a feature you want but don’t see? &lt;a href=&quot;https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin&quot;&gt;Build it in yourself,&lt;/a&gt; or choose from a huge &lt;a href=&quot;https://obsidian.md/plugins&quot;&gt;library of options.&lt;/a&gt; Someone might have already solved your problem for you! It has the ability to be everything for some people, and only &lt;em&gt;just enough&lt;/em&gt; for others, but enables users to have granular controls over their experiences.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;a href=&quot;https://culturedcode.com/things/&quot;&gt;Things&lt;/a&gt; is just a solid, beautiful-looking to-do app I’ve been using for years. It does exactly what it says on the tin, and it has never failed me. Unlike a lot of other apps in that space, the closest it gets to co-mingling with other products is integrating with your calendar so you can see a list of your calendar events while looking at your to-do list for the day, which is all I need. And even then, it doesn’t show up where it isn’t welcome.&lt;br&gt;&lt;br&gt;I also really like what the folks at &lt;a href=&quot;https://notbor.ing/&quot;&gt;!Boring&lt;/a&gt; do. They have a whole suite of apps in their style that each do one specific thing: a weather app, a calculator app, a timer app, etc.&lt;br&gt;&lt;br&gt;– Jillian&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;h3&gt;Looking to the Future&lt;/h3&gt;&lt;p&gt;The broad takeaway, from our perspective, is while it’s easy to fall into the trap of chasing trends or wanting your app to be everything at once, the best products with the most longevity are proven to be the ones with thoughtful, intentional scope.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;As a team that’s built and done a bit of everything across the gamut, helping people figure that out is &lt;a href=&quot;https://lickability.com/services/&quot;&gt;part of what we do!&lt;/a&gt;&lt;/p&gt;  &lt;/aside&gt;&lt;p&gt;&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>GitHub Markdown Shortcuts</title><link>https://lickability.com/blog/github-markdown-shortcuts/</link><guid isPermaLink="true">https://lickability.com/blog/github-markdown-shortcuts/</guid><description>Streamlining our pull requests</description><pubDate>Wed, 24 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This summer, Lickability held a brief &lt;a href=&quot;https://mastodon.social/@lickability/115135690091181768&quot;&gt;retreat&lt;/a&gt; where engineers hacked together a few projects to scratch itches, learn new technologies, and work together. I took the opportunity to make a few shortcuts (that is, referring to Apple’s &lt;a href=&quot;https://support.apple.com/guide/shortcuts-mac/intro-to-shortcuts-apdf22b0444c/mac&quot;&gt;Shortcuts app&lt;/a&gt;) to boost productivity around my favorite thing: pull requests!&lt;/p&gt;&lt;p&gt;If you’ve been reading our blog for a while, you’ll already know I feel strongly about &lt;a href=&quot;https://lickability.com/blog/how-our-engineers-collaborate/#make-your-work-easy-to-review&quot;&gt;good pull request descriptions&lt;/a&gt;, and &lt;a href=&quot;https://lickability.com/blog/how-we-smoke-test-pull-requests-with-git-revert/&quot;&gt;making things easier for reviewers&lt;/a&gt;. So while others toiled away in Swift, I got to work on three new shortcuts that our team has since worked into their daily routines. Before we get to those, a little history…&lt;/p&gt;&lt;h2&gt;Baby’s First Shortcut&lt;/h2&gt;&lt;p&gt;For as long as I‘ve been using GitHub, I’ve had a pet peeve about how enormously iPhone screenshots are rendered by default when attaching them to pull requests and issues. Sure, the screenshots are saved @2x, but that effectively results in double-sized images, massively interrupting the flow of text.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;An example pull request on GitHub that shows an iOS Simulator screenshot in the description at full size, taking up the full width of the PR so you have to scroll down to see the whole image.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/93ac243aba26b7cd0a1fdfe39ff74926884aaa86-2890x2146.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/93ac243aba26b7cd0a1fdfe39ff74926884aaa86-2890x2146.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/93ac243aba26b7cd0a1fdfe39ff74926884aaa86-2890x2146.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/93ac243aba26b7cd0a1fdfe39ff74926884aaa86-2890x2146.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/93ac243aba26b7cd0a1fdfe39ff74926884aaa86-2890x2146.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/93ac243aba26b7cd0a1fdfe39ff74926884aaa86-2890x2146.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/93ac243aba26b7cd0a1fdfe39ff74926884aaa86-2890x2146.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/93ac243aba26b7cd0a1fdfe39ff74926884aaa86-2890x2146.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2890 2890w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/93ac243aba26b7cd0a1fdfe39ff74926884aaa86-2890x2146.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1188&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Over the years, I developed various strategies to combat this, including automated scripts that resized iPhone screenshots that were saved to my Desktop, but I was never happy with destroying the quality of images when in many cases the screenshots were used to highlight tweaks in designs that would benefit from pixel perfection. I sought a more robust approach. A few years back, dropping a screenshot into GitHub looked something like this:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Markdown&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;![&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;IMG_E01132EEBBE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;](&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4;text-decoration:underline&quot;&gt;https://github.com/user-attachments/assets/c47aec88-87cc-4f8a-9a40-ba281d951e0b&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;While we can’t specify a size in Markdown syntax directly, using absolute-beginner HTML, we can easily render the image at that URL at a more appropriate size using an &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;img&lt;/code&gt; tag, &lt;a href=&quot;https://daringfireball.net/projects/markdown/syntax#html&quot;&gt;since after all&lt;/a&gt;:&lt;/p&gt;&lt;blockquote&gt;For any markup that is not covered by Markdown’s syntax, you simply use HTML itself. There’s no need to preface it or delimit it to indicate that you’re switching from Markdown to HTML; you just use the tags.&lt;/blockquote&gt;&lt;p&gt;So with a very basic text transformation, we can limit the width of the rendered image within a pull request description or issue, maintaining the aspect ratio:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;HTML&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;img&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; src&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;https://github.com/user-attachments/assets/c47aec88-87cc-4f8a-9a40-ba281d951e0b&quot;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;300&quot;&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;I use a width of &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;300&lt;/code&gt; for iPhone screenshots, regardless of device size, as I find it to be a nice balance between legibility within the image and not being too disruptive to surrounding text.&lt;/p&gt;&lt;p&gt;&lt;em&gt;But you said you weren’t happy with destroying the quality of images!&lt;/em&gt; That’s right, straw man. The benefit of this approach is that the image quality is preserved, and the full-size image just a click or two away in your browser.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A gif showing an example of a GitHub pull request where the image in the description has been resized using our Shortcut. In the gif, we show an example of right clicking to open the image in a new tab so you can zoom in and see it at its full size.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/008da2f7201ac0109a494bdf5871a272e517909a-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/008da2f7201ac0109a494bdf5871a272e517909a-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/008da2f7201ac0109a494bdf5871a272e517909a-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/008da2f7201ac0109a494bdf5871a272e517909a-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/008da2f7201ac0109a494bdf5871a272e517909a-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800&quot; width=&quot;800&quot; height=&quot;644&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;&lt;em&gt;But isn’t that cumbersome to do for every image?&lt;/em&gt; Sure is, but like any engineer, I saw that as an opportunity to automate away the tedium of text transformation. With a bit of free time, I decided to dip my toes into Shortcuts to tackle this since, at the time, it was relatively new on Mac, and I wanted an excuse to try it out. Not long after, I had a working shortcut that could handle the resizing task with ease, conveniently located in the Services and right-click menus, even tackling multiple images at once!&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A gif that shows how to use the &amp;quot;Limit GitHub Image Width to 300&amp;quot; Shortcut by selecting the image text when editing your GitHub pull request, right clicking to open a menu, and selecting the Shortcut as an option in the menu. In this example, we highlighted two images at once to show how the Shortcut can edit more than one image at a time. In the pull request preview tab, both images are now 300px wide and appear side-by-side.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9d05fc45f169ddabe5c1404b2691d6fdf8984e90-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/9d05fc45f169ddabe5c1404b2691d6fdf8984e90-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/9d05fc45f169ddabe5c1404b2691d6fdf8984e90-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/9d05fc45f169ddabe5c1404b2691d6fdf8984e90-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9d05fc45f169ddabe5c1404b2691d6fdf8984e90-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800&quot; width=&quot;800&quot; height=&quot;644&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Pretty nifty! If you think you‘d benefit from this, you can grab the shortcut from the link at the bottom of this post.&lt;/p&gt;&lt;p&gt;GitHub has since changed how it processes image uploads, and more conveniently provides an &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;img&lt;/code&gt; tag on upload:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;HTML&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;img&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;1179&quot;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; height&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;2556&quot;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; alt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Simulator Screenshot - iPhone 16 - 2025-09-09 at 15 57 27&quot;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; src&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;https://github.com/user-attachments/assets/830ef400-8619-49a6-86e2-536690ac2d9b&quot;&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;So despite this being a convenient shortcut for years, it’s now much simpler to just delete the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;height&lt;/code&gt; parameter, and restrict the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;width&lt;/code&gt; parameter to a value of your choosing. Small bummer for my shortcut’s usefulness, but that’s a win for everyone. I still use the shortcut to this day, as I find it convenient to resize many images at once, but I do appreciate GitHub’s constant improvements to the text editing experience.&lt;/p&gt;&lt;h2&gt;The New Shortcuts&lt;/h2&gt;&lt;p&gt;In the intervening years, I noticed some tedious patterns creeping in, ultimately requiring me to take extra time to communicate my thoughts in pull request descriptions. I had been saving up some ideas for the next hackathon-style work event, and our retreat this summer gave me the opportunity I needed. In an afternoon, I created the shortcuts that follow, and I’ve been saving lots of time ever since!&lt;/p&gt;&lt;h3&gt;Arranging Related Screenshots in Tables&lt;/h3&gt;&lt;p&gt;I &lt;em&gt;love&lt;/em&gt; &lt;a href=&quot;https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables&quot;&gt;Markdown tables&lt;/a&gt; for neatly organizing data. Historically, though, they’ve been a bit of a pain to type up manually, often requiring switching between Write and Preview modes to ensure you’ve got the syntax just right. Fortunately, &lt;a href=&quot;https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/about-slash-commands&quot;&gt;GitHub recently introduced slash commands&lt;/a&gt;, including &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;/table&lt;/code&gt;, which allows you to specify the number of columns and rows, giving you a great starting point to fill in your relevant data.&lt;/p&gt;&lt;p&gt;When writing PR descriptions, I‘ll often include multiple screenshots, sometimes “Before and After” comparisons, and in the spirit of not disrupting the surrounding text too much, I like to arrange these side-by-side. Using the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;/table&lt;/code&gt; slash command to neatly package the screenshots together is nice, but still requires a fair bit of manual manipulation, especially when resizing the images is also necessary. So I thought, why not create a shortcut to do &lt;em&gt;all&lt;/em&gt; of the heavy lifting?&lt;/p&gt;&lt;p&gt;Now, with a single activation of my “Make GitHub Image Markdown Table” shortcut, I can take multiple screenshots, specify column headers, and arrange them in a beautiful table in seconds. I’ve even built in some conveniences, including a “Before and After” option that’s offered when exactly two screenshots are selected. And to build off of my first shortcut, image resizing is built in by default.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A gif that shows an example of how to use our &amp;quot;Make GitHub Image Markdown Table&amp;quot; Shortcut by selecting the text of multiple images at a time when editing a GitHub pull request, right clicking to open the menu, and selecting the Shortcut from the menu. When running this Shortcut, a pop-up opens to prompt you to select which table headers you&amp;#x27;d like to use from a list that includes options for &amp;quot;Default,&amp;quot; &amp;quot;Before and After,&amp;quot; or &amp;quot;Custom.&amp;quot; In this example, we select &amp;quot;Before and After,&amp;quot; and the two images are put into a markdown table with a &amp;quot;Before&amp;quot; header and an &amp;quot;After&amp;quot; header.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6567c80ff150cc2863dd66b94b65792d2de52e52-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/6567c80ff150cc2863dd66b94b65792d2de52e52-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/6567c80ff150cc2863dd66b94b65792d2de52e52-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/6567c80ff150cc2863dd66b94b65792d2de52e52-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6567c80ff150cc2863dd66b94b65792d2de52e52-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800&quot; width=&quot;800&quot; height=&quot;644&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Wrap Details in a Toggle&lt;/h3&gt;&lt;p&gt;Have a lot of optional background context to write? Screenshots and videos still taking up too much space, distracting from the main points you’re trying to get across? No problem. You can easily hide additional details behind a heading with a toggle used to show/hide your more verbose context.&lt;/p&gt;&lt;p&gt;This is accomplished purely with HTML tags, like the following:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;HTML&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;details&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;The boring details…&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;details&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A gif showing a preview of a GitHub pull request with the text &amp;quot;The boring details...&amp;quot; as the title of a details toggle. When the toggle is clicked, it opens to show some Lorem Ipsum text.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6cc118e5a8ed263aaa6e5f1373312e5343ae1e89-2000x817.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/6cc118e5a8ed263aaa6e5f1373312e5343ae1e89-2000x817.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/6cc118e5a8ed263aaa6e5f1373312e5343ae1e89-2000x817.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/6cc118e5a8ed263aaa6e5f1373312e5343ae1e89-2000x817.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/6cc118e5a8ed263aaa6e5f1373312e5343ae1e89-2000x817.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6cc118e5a8ed263aaa6e5f1373312e5343ae1e89-2000x817.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;654&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;While there’s a newly introduced &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;/details&lt;/code&gt; slash command for this as well, I much prefer interacting with a lightweight UI over editing the text between HTML tags, if I can help it. Enter my “Wrap in Toggle” shortcut to save the day:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A gif showing an example of how to use our &amp;quot;Wrap In Toggle&amp;quot; Shortcut by selecting some text and running the Shortcut. When run, the Shortcut shows a pop-up that asks you what you&amp;#x27;d like the title of the toggle to be. We type in text that says &amp;quot;The boring details...&amp;quot; and the Shortcut puts our selected text into an HTML details tag with our title as the summary.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6b5f8d8a0df6118ee464ae05f9bdf8486d197640-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/6b5f8d8a0df6118ee464ae05f9bdf8486d197640-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/6b5f8d8a0df6118ee464ae05f9bdf8486d197640-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/6b5f8d8a0df6118ee464ae05f9bdf8486d197640-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6b5f8d8a0df6118ee464ae05f9bdf8486d197640-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800&quot; width=&quot;800&quot; height=&quot;644&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Wrap in Callout&lt;/h3&gt;&lt;p&gt;If I had a nickel for every time another engineer looked at a neatly formatted callout in a PR description of mine and said “how’d you do that??,” I’d have… some nickels.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;An example of a callout in a GitHub pull request. The callout is red, with a &amp;quot;Caution&amp;quot; label, and says, &amp;quot;Let&amp;#x27;s hold off on merging this until after t he rebrand is complete!&amp;quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9282d77771d9345e359c7a6b398a6f419ae747c0-1934x526.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/9282d77771d9345e359c7a6b398a6f419ae747c0-1934x526.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/9282d77771d9345e359c7a6b398a6f419ae747c0-1934x526.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/9282d77771d9345e359c7a6b398a6f419ae747c0-1934x526.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/9282d77771d9345e359c7a6b398a6f419ae747c0-1934x526.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1934 1934w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9282d77771d9345e359c7a6b398a6f419ae747c0-1934x526.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;435&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;I’ve had &lt;a href=&quot;https://github.com/orgs/community/discussions/16925&quot;&gt;this page&lt;/a&gt; bookmarked for a couple years, as I often forget the exact syntax and need a refresher. To streamline this — you guessed it — I created a “Wrap in Callout” shortcut. You know the drill by now: write some text, select it, run the shortcut, &lt;em&gt;DONE&lt;/em&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A gif showing an example of how to use our &amp;quot;Wrap in Callout&amp;quot; Shortcut by selecting some text and running the Shortcut. When run, the Shortcut prompts you to pick from a list of callout types (Note, Tip, Important, Warning, and Caution) and puts the text into the type of callout you selected.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/07796af15ec677476c3dd9296ff3b3fe69408aae-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/07796af15ec677476c3dd9296ff3b3fe69408aae-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/07796af15ec677476c3dd9296ff3b3fe69408aae-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/07796af15ec677476c3dd9296ff3b3fe69408aae-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/07796af15ec677476c3dd9296ff3b3fe69408aae-800x644.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800&quot; width=&quot;800&quot; height=&quot;644&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Admittedly, GitHub pretty much steals all the thunder on this one with a new &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;/alerts&lt;/code&gt; slash command, but I like the convenience of having all of my text manipulation shortcuts in one spot, and being able think &lt;em&gt;text-first&lt;/em&gt; before deciding to move something into a callout / alert.&lt;/p&gt;&lt;h2&gt;Just Give Me The Shortcuts!&lt;/h2&gt;&lt;p&gt;Here they are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://www.icloud.com/shortcuts/c9fd8f413346450cbafa93182ee17f8b&quot;&gt;Limit GitHub Image Width to 300&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.icloud.com/shortcuts/2d267589878b4caba92db7002e45bf8a&quot;&gt;Make GitHub Image Markdown Table&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.icloud.com/shortcuts/6f3e6d3daeed49d48e1988dd372db2e2&quot;&gt;Wrap in Toggle&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.icloud.com/shortcuts/e25e3c2f195e4ceb9a532305c897af97&quot;&gt;Wrap in Callout&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;To add them to the Services and right-click menus, open each shortcut, select the &lt;strong&gt;Shortcut Details&lt;/strong&gt; inspector panel (1), the &lt;strong&gt;Details&lt;/strong&gt; tab (2), and check &lt;strong&gt;Use as Quick Action&lt;/strong&gt;, &lt;strong&gt;Services Menu&lt;/strong&gt;, and &lt;strong&gt;Provide Output&lt;/strong&gt; (3). You could also assign a keyboard shortcut if that’s more your style.&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the menu in the Mac Shortcuts app. The Shortcut Details inspector panel is highlighted with the number 1 next to it, the Details tab is highlighted with the number 2 next to it, and the &amp;quot;Use as Quick Action,&amp;quot; &amp;quot;Services Menu,&amp;quot; and &amp;quot;Provide Output&amp;quot; items in the Details tab are checked off and highlighted with the number 3 next to them.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b2dc8962fe2ba8b8f08183be0c526234483d1960-564x754.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/b2dc8962fe2ba8b8f08183be0c526234483d1960-564x754.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/b2dc8962fe2ba8b8f08183be0c526234483d1960-564x754.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=564 564w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b2dc8962fe2ba8b8f08183be0c526234483d1960-564x754.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;535&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;By all means, please let us know if you find these useful in your day-to-day GitHub adventures. Feel free to edit them, share them, or suggest improvements. &lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Have a non-Shortcuts approach that you really like for this kinda thing? &lt;a href=&quot;https://mastodon.social/@lickability&quot;&gt;Let us know!&lt;/a&gt;&lt;/p&gt;  &lt;/aside&gt;&lt;p&gt;&lt;/p&gt; </content:encoded><author>Michael Liberatore</author></item><item><title>Predictive Code Completion in Xcode</title><link>https://lickability.com/blog/xcode-predictive-code-completion/</link><guid isPermaLink="true">https://lickability.com/blog/xcode-predictive-code-completion/</guid><description>Getting started &amp; first impressions</description><pubDate>Wed, 11 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Like it or not, AI assistance in software development is here to stay for the foreseeable future. If you’ve embraced technologies like ChatGPT to answer coding questions, format mock data, or take the first crack at writing a new feature, you’ve likely seen an overall boost to your productivity. If you’re a Swift developer and you haven’t dipped your toes in just yet — either due to privacy and security concerns, or mixed results with generative AI in other contexts — you may want to take another look, as major players have now entered the ring that offer AI assistance directly within Xcode.&lt;/p&gt;&lt;p&gt;This year, Apple’s first-party offering, its privacy-focused &lt;a href=&quot;https://developer.apple.com/documentation/xcode-release-notes/xcode-16-release-notes&quot;&gt;Xcode Code Completion model&lt;/a&gt; was introduced, and is now out of beta. Following this, GitHub announced a public preview of &lt;a href=&quot;https://github.blog/changelog/2024-10-29-github-copilot-code-completion-in-xcode-is-now-available-in-public-preview/&quot;&gt;Copilot for Xcode&lt;/a&gt;, ChatGPT opened an early beta of its &lt;a href=&quot;https://help.openai.com/en/articles/10119604-work-with-apps-on-macos&quot;&gt;Works with Apps&lt;/a&gt; feature that is Xcode-compatible, and &lt;a href=&quot;https://www.topview.ai/blog/detail/cursor-ai-how-good-is-it-for-swift-and-ios&quot;&gt;Cursor&lt;/a&gt; (albeit in a separate IDE) is gaining steam in the community. In this post, we’ll look specifically at Xcode’s new code completion model, how to set it up, and what it can do to improve your productivity.&lt;/p&gt;&lt;h3&gt;Prerequisites&lt;/h3&gt;&lt;p&gt;To take advantage of Xcode’s predictive code completion, you’ll need to be up-to-date software-wise. The feature was introduced first in Xcode 16, and while Xcode 16 runs just fine on macOS Sonoma, the latest version of macOS (Sequoia) is required to use the code completion feature. Hardware-wise, you’ll need an Apple Silicon-based computer and at least 8 GB of memory, in addition to about 2 GB of additional disk space for the model itself.&lt;/p&gt;&lt;p&gt;So in summary, that’s:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;macOS Sequoia or later&lt;/li&gt;&lt;li&gt;Xcode 16 or later&lt;/li&gt;&lt;li&gt;Apple Silicon-based computer&lt;/li&gt;&lt;li&gt;8 GB of memory&lt;/li&gt;&lt;li&gt;2 GB of free disk space&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Installation&lt;/h3&gt;&lt;p&gt;Xcode’s predictive code completion feature has all the advantages of being a first-party solution, including being dead simple to install. When launching Xcode 16 or later for the first time, you’ll see a window that allows you to select components to install right away. Check &lt;strong&gt;Predictive Code Completion Model&lt;/strong&gt; and any platforms you’ll be using before clicking &lt;strong&gt;Download &amp;amp; Install&lt;/strong&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Xcode’s first launch window, which includes a checkbox for downloading the predictive code completion model.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6fad9fee98ca091bf534babce13a4e0d1aa7338b-1224x1514.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=306 306w, https://cdn.sanity.io/images/nkt6o869/production/6fad9fee98ca091bf534babce13a4e0d1aa7338b-1224x1514.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=612 612w, https://cdn.sanity.io/images/nkt6o869/production/6fad9fee98ca091bf534babce13a4e0d1aa7338b-1224x1514.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=918 918w, https://cdn.sanity.io/images/nkt6o869/production/6fad9fee98ca091bf534babce13a4e0d1aa7338b-1224x1514.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1224 1224w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6fad9fee98ca091bf534babce13a4e0d1aa7338b-1224x1514.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1224&quot; width=&quot;1224&quot; height=&quot;1514&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p data-astro-cid-c6ccksbc&gt;Xcode’s first launch window, which includes a checkbox for downloading the predictive code completion model.&lt;/p&gt; &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Don’t worry if you’ve opted not to download this on first launch. Simply navigate to &lt;strong&gt;Settings → Components → Predictive Code Completion Model&lt;/strong&gt;, and click the &lt;strong&gt;Get&lt;/strong&gt; button to download and install the model.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Xcode’s settings showing an option to install the predictive code completion model after initial launch.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b21410915aa07cd9790acaf2c20b388651945c00-1884x1540.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/b21410915aa07cd9790acaf2c20b388651945c00-1884x1540.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/b21410915aa07cd9790acaf2c20b388651945c00-1884x1540.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/b21410915aa07cd9790acaf2c20b388651945c00-1884x1540.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/b21410915aa07cd9790acaf2c20b388651945c00-1884x1540.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1884 1884w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b21410915aa07cd9790acaf2c20b388651945c00-1884x1540.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1308&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;And that’s it! You can turn the feature off at any time by navigating to &lt;strong&gt;Settings → Text Editing → Editing&lt;/strong&gt; and unchecking &lt;strong&gt;Predictive code completion&lt;/strong&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Xcode’s settings showing an option to toggle predictive code completion on and off.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/69376ca18117cd840f7d63f16977d360575dc3fa-1884x1344.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/69376ca18117cd840f7d63f16977d360575dc3fa-1884x1344.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/69376ca18117cd840f7d63f16977d360575dc3fa-1884x1344.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/69376ca18117cd840f7d63f16977d360575dc3fa-1884x1344.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/69376ca18117cd840f7d63f16977d360575dc3fa-1884x1344.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1884 1884w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/69376ca18117cd840f7d63f16977d360575dc3fa-1884x1344.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1141&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Code completion in action&lt;/h3&gt;&lt;p&gt;To take this new feature for a spin, we wrote an implementation of a “take home” code exercise that we’ve given engineering team candidates in the past. Nothing too complex:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Hit a specific endpoint to download photos&lt;/li&gt;&lt;li&gt;Display those photos in a scrolling grid&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Immediately, we can see that Xcode’s predictive code completion has clear advantages to being built in, compared to the other AI-assisted code features we’ve tried. There will always be a barrier for third-parties to achieve what Apple can do, since they’re not bound by the limitations of Xcode extensions, or the need to run in an entirely separate interface. Xcode’s suggestions appear while typing, similar to snippets, completing the current line and suggesting more of an implementation to follow (truncated with “…”):&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Xcode’s predictive code completion suggesting the type of a computed path variable declaration from a protocol. The implementation of the property is truncated with an ellipsis.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9deffd9869a0b20e0a0dddc9906a0c4cae68e943-668x269.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=334 334w, https://cdn.sanity.io/images/nkt6o869/production/9deffd9869a0b20e0a0dddc9906a0c4cae68e943-668x269.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=668 668w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9deffd9869a0b20e0a0dddc9906a0c4cae68e943-668x269.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=668&quot; width=&quot;668&quot; height=&quot;269&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Xcode’s predictive code completion suggesting the type of a computed &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;path&lt;/code&gt; variable declaration from a protocol. The implementation of the property is truncated with “…”&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;One stroke of the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;tab&lt;/code&gt; key will complete the line, and may suggest the body or implementation of the declaration:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Xcode’s predictive code completion suggesting the body of the computed path property.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a38410fda93c1416998e65fe74b02e051effda09-668x321.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=334 334w, https://cdn.sanity.io/images/nkt6o869/production/a38410fda93c1416998e65fe74b02e051effda09-668x321.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=668 668w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a38410fda93c1416998e65fe74b02e051effda09-668x321.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=668&quot; width=&quot;668&quot; height=&quot;321&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p data-astro-cid-c6ccksbc&gt;Xcode’s predictive code completion suggesting the body of the computed path property.&lt;/p&gt; &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;If you’re satisfied with the suggestion, or if it provides a good enough starting point, one more &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;tab&lt;/code&gt; will accept it.&lt;/p&gt;&lt;p&gt;Code predictions blend fairly well into your coding interface. However, I’ve found that they can be a bit visually distracting when you stop to think about what you’re typing and &lt;em&gt;aren’t&lt;/em&gt; looking for suggestions. Constantly turning the feature on and off when you need vs. don’t need it is a non-starter for me, so I like to leave it on, and hope I’ll get used to the additional visual noise.&lt;/p&gt;&lt;h4&gt;Suggestion quality&lt;/h4&gt;&lt;p&gt;No AI-assisted coding tools can read your mind, and I highly recommend using them only as a start to get &lt;em&gt;something&lt;/em&gt; working, and then refine the code manually. They’ll produce erroneous code, off-the-wall suggestions, and use bad practices at times. Xcode’s new code completion model is no exception — we shouldn’t expect it to get things perfect every time. It’s amazing that it works at all, but still, it can get frustrating to use if you expect too much.&lt;/p&gt;&lt;p&gt;Overall, I haven’t found it useful to rely on predictive suggestions for top-level declarations. Even with strict patterns in place in a codebase, and even with types that declare conformance to existing protocols, I very rarely find the suggestions useful. Again, it can’t read your mind, but you can’t be blamed for expecting more when there’s a lot of context to be gleaned from the rest of your codebase. For example, our &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Store&lt;/code&gt; protocol has several required properties and methods, and each store implementation follows a convention of a &lt;em&gt;single&lt;/em&gt; &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;@Published&lt;/code&gt; &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;state&lt;/code&gt; property. That’s just too much to ask at this time, and we’d be better off simply using Xcode’s “fix-it” suggestions to add protocol stubs.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Xcode’s predictive code completion suggesting a workable start to a “store” class, but failing to follow patterns already set forth in the codebase.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f512f7c933fb88a9dac0afd96eb54079e39d3fa0-772x439.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=193 193w, https://cdn.sanity.io/images/nkt6o869/production/f512f7c933fb88a9dac0afd96eb54079e39d3fa0-772x439.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=386 386w, https://cdn.sanity.io/images/nkt6o869/production/f512f7c933fb88a9dac0afd96eb54079e39d3fa0-772x439.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=579 579w, https://cdn.sanity.io/images/nkt6o869/production/f512f7c933fb88a9dac0afd96eb54079e39d3fa0-772x439.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=772 772w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f512f7c933fb88a9dac0afd96eb54079e39d3fa0-772x439.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=772&quot; width=&quot;772&quot; height=&quot;439&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p data-astro-cid-c6ccksbc&gt;Xcode’s predictive code completion suggesting a workable start to a “store” class, but failing to follow patterns already set forth in the codebase.&lt;/p&gt; &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Additionally, without more context in a Swift file than the default header, we frequently see Xcode’s predictive code completion try to introduce tests where they don’t belong. In this example, we’re trying to create a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Photo&lt;/code&gt; type in a project with no prior test module whatsoever:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/efa712fb15632826a76fd6e39a5f559457de0aa0-898x275.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=225 225w, https://cdn.sanity.io/images/nkt6o869/production/efa712fb15632826a76fd6e39a5f559457de0aa0-898x275.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=449 449w, https://cdn.sanity.io/images/nkt6o869/production/efa712fb15632826a76fd6e39a5f559457de0aa0-898x275.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=674 674w, https://cdn.sanity.io/images/nkt6o869/production/efa712fb15632826a76fd6e39a5f559457de0aa0-898x275.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=898 898w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/efa712fb15632826a76fd6e39a5f559457de0aa0-898x275.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=898&quot; width=&quot;898&quot; height=&quot;275&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Xcode’s predictive code completion hellbent on suggesting test declarations in a file named Photo.swift.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/efc527f314fae9e25b0b4269a67de5b11a65ebf2-804x278.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=201 201w, https://cdn.sanity.io/images/nkt6o869/production/efc527f314fae9e25b0b4269a67de5b11a65ebf2-804x278.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=402 402w, https://cdn.sanity.io/images/nkt6o869/production/efc527f314fae9e25b0b4269a67de5b11a65ebf2-804x278.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=603 603w, https://cdn.sanity.io/images/nkt6o869/production/efc527f314fae9e25b0b4269a67de5b11a65ebf2-804x278.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=804 804w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/efc527f314fae9e25b0b4269a67de5b11a65ebf2-804x278.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=804&quot; width=&quot;804&quot; height=&quot;278&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Xcode’s predictive code completion hellbent on suggesting test declarations in a file named &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Photo.swift&lt;/code&gt;.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;We have a lot more success applying suggestions to small, missing implementations, and working carefully to refine them.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Xcode’s predictive code completion suggesting a Text displaying an Error and a Button to retry a network request within a SwiftUI view. It’s a good starting point.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/3b21d9f3b4a3fb19fb0294d0c1a642c9f2858980-1118x293.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=280 280w, https://cdn.sanity.io/images/nkt6o869/production/3b21d9f3b4a3fb19fb0294d0c1a642c9f2858980-1118x293.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=559 559w, https://cdn.sanity.io/images/nkt6o869/production/3b21d9f3b4a3fb19fb0294d0c1a642c9f2858980-1118x293.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=839 839w, https://cdn.sanity.io/images/nkt6o869/production/3b21d9f3b4a3fb19fb0294d0c1a642c9f2858980-1118x293.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1118 1118w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/3b21d9f3b4a3fb19fb0294d0c1a642c9f2858980-1118x293.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1118&quot; width=&quot;1118&quot; height=&quot;293&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Xcode’s predictive code completion suggesting a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; displaying an &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Error&lt;/code&gt; and a &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Button&lt;/code&gt; to retry a network request within a SwiftUI view. It’s a good starting point.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Frequently, even within some quality suggestions, Xcode suggests snippets that include symbols that don’t exist. I don’t see this as too much of a problem, as in simple use cases, the suggestions tend to include types that simply don’t exist &lt;em&gt;yet&lt;/em&gt;, and can somewhat serve as a suggestion to create them. Still, it’d be nice if it were sophisticated enough to create them for you. In the example below, the type &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Photo​Row&lt;/code&gt; doesn’t exist yet, but our properly factored final implementation of a photo list would likely use a specific type to wrap the layout of individual photos.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;An accepted suggestion of a scrolling list of PhotoRows, a type that doesn’t exist in our codebase.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/5358a04f6207f22c39030b85cf81d51af9327eed-574x272.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=287 287w, https://cdn.sanity.io/images/nkt6o869/production/5358a04f6207f22c39030b85cf81d51af9327eed-574x272.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=574 574w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/5358a04f6207f22c39030b85cf81d51af9327eed-574x272.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=574&quot; width=&quot;574&quot; height=&quot;272&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;An accepted suggestion of a scrolling list of &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Photo​Row&lt;/code&gt;s, a type that doesn’t exist in our codebase.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Xcode’s suggestions can be improved by pre-commenting expected logic in plain English. By adding a comment specifically mentioning “grid” and the number of columns, we get closer to the actual structure we want.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;An early refinement attempt at getting Xcode’s predictive code completion to create a scrolling grid of photos. Xcode suggests a LazyHGrid initialized with columns. That API does not exist. Additionally, a parameter on the same line is not given a value.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/5e679a455d0912d9989ab0a88c9ba54a0043f4c9-1610x309.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/5e679a455d0912d9989ab0a88c9ba54a0043f4c9-1610x309.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/5e679a455d0912d9989ab0a88c9ba54a0043f4c9-1610x309.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/5e679a455d0912d9989ab0a88c9ba54a0043f4c9-1610x309.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/5e679a455d0912d9989ab0a88c9ba54a0043f4c9-1610x309.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1610 1610w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/5e679a455d0912d9989ab0a88c9ba54a0043f4c9-1610x309.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;307&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;An early refinement attempt at getting Xcode’s predictive code completion to create a scrolling grid of photos. Xcode suggests a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Lazy​H​Grid&lt;/code&gt; initialized with &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;columns&lt;/code&gt;. That API does not exist. Additionally, a parameter on the same line is not given a value.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;With enough refinement of the comments, we can usually get Xcode to suggest something workable, but in simple cases like this that require multiple attempts, it’s hard to say whether we’re saving any time vs. writing the code ourselves.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;By specifying “vertically scrolling grid” and “3 columns wide,” in our comment we were able to get Xcode to suggest a quality start to our photo grid.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a8aa1a6ea215ff27093f0222691bb74ce8580b43-1266x308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=317 317w, https://cdn.sanity.io/images/nkt6o869/production/a8aa1a6ea215ff27093f0222691bb74ce8580b43-1266x308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=633 633w, https://cdn.sanity.io/images/nkt6o869/production/a8aa1a6ea215ff27093f0222691bb74ce8580b43-1266x308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=950 950w, https://cdn.sanity.io/images/nkt6o869/production/a8aa1a6ea215ff27093f0222691bb74ce8580b43-1266x308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1266 1266w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a8aa1a6ea215ff27093f0222691bb74ce8580b43-1266x308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1266&quot; width=&quot;1266&quot; height=&quot;308&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p data-astro-cid-c6ccksbc&gt;By specifying “vertically scrolling grid” and “3 columns wide,” in our comment we were able to get Xcode to suggest a quality start to our photo grid.&lt;/p&gt; &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;The future&lt;/h3&gt;&lt;p&gt;At the time of writing, Xcode’s built-in code completion model is far more limited in functionality than AI-assisted tools that work with other IDEs. But even if we’re late to the party as Apple platform developers, the future is looking bright. At WWDC24, &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2024/102/?time=1438&quot;&gt;Apple announced&lt;/a&gt; that Swift Assist would be coming later this year — Swift Assist provides an in-editor text field for the developer to make arbitrary requests to write code for you, with an emphasis on prototyping.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Screenshot demoing Swift Assist from Apple’s WWDC24 during Platforms State of the Union.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/abd350fb92e881eed0e3177808033286b02e36f8-2561x1441.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/abd350fb92e881eed0e3177808033286b02e36f8-2561x1441.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/abd350fb92e881eed0e3177808033286b02e36f8-2561x1441.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/abd350fb92e881eed0e3177808033286b02e36f8-2561x1441.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/abd350fb92e881eed0e3177808033286b02e36f8-2561x1441.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/abd350fb92e881eed0e3177808033286b02e36f8-2561x1441.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/abd350fb92e881eed0e3177808033286b02e36f8-2561x1441.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2561 2561w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/abd350fb92e881eed0e3177808033286b02e36f8-2561x1441.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;900&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Screenshot demoing Swift Assist from Apple’s WWDC24 during &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2024/102&quot;&gt;Platforms State of the Union&lt;/a&gt;.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;Is it worth using now?&lt;/h3&gt;&lt;p&gt;I think so! For the time being, I’ll be using a mix of Xcode’s code completion for the basics, and I’ll keep ChatGPT in my dock and at-the-ready for broader and more complex assistance. Over time, I hope to be able to lean first-party, and use only Xcode’s built-in features. With Apple’s outsized focus on privacy and security, and the built-in nature of the features, I could look past some small shortcomings for the benefits of convenience. The pace at which competitors in the space are improving for other languages and IDEs does make me worry that Apple will struggle to keep up, but that remains to be seen.&lt;/p&gt; </content:encoded><author>Michael Liberatore</author></item><item><title>RevenueCat App for iOS Launches</title><link>https://lickability.com/blog/revenuecat-app/</link><guid isPermaLink="true">https://lickability.com/blog/revenuecat-app/</guid><description>Subscription metrics in your pocket</description><pubDate>Wed, 23 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Earlier this year, our &lt;a href=&quot;/blog/revenuecat&quot; title=&quot;We’re a RevenueCat partner!&quot; data-astro-cid-okzjwv6e&gt;longtime pals and partners&lt;/a&gt;  at &lt;a href=&quot;https://revenuecat.com&quot;&gt;RevenueCat&lt;/a&gt; approached us about what it would take to build a dashboard app for iOS, and we jumped at the chance to work together. RevenueCat is the leading provider of subscription technology, helping developers create and ship great in-app purchase experiences, manage customer data, and grow their revenue. We’re big fans of the service and constantly recommend it to our clients looking to monetize their apps.&lt;/p&gt;&lt;p&gt;For years, a few third-party apps let developers easily check their stats on the go, but there has never been an official mobile app…until today. &lt;a href=&quot;https://apps.apple.com/us/app/revenuecat-mobile/id6504531798&quot;&gt;RevenueCat for iOS&lt;/a&gt; is designed to help developers manage their subscription business on the go. You can get real-time transaction notifications, track key metrics with widgets, and monitor app performance anywhere. The app is free and works with any RevenueCat account, whether you’re on a paid or free plan. Want to see it in action? Check out this &lt;a href=&quot;https://www.revenuecat.com/blog/company/new-revenuecat-ios-ap/&quot;&gt;blog post&lt;/a&gt; and introduction video from RevenueCat themselves:&lt;/p&gt;&lt;div class=&quot;not-prose&quot;&gt; &lt;style&gt;astro-island,astro-slot,astro-static-slot{display:contents}&lt;/style&gt;&lt;script&gt;(()=&gt;{var l=(s,i,o)=&gt;{let r=async()=&gt;{await(await s())()},t=typeof i.value==&quot;object&quot;?i.value:void 0,c={rootMargin:t==null?void 0:t.rootMargin},n=new IntersectionObserver(e=&gt;{for(let a of e)if(a.isIntersecting){n.disconnect(),r();break}},c);for(let e of o.children)n.observe(e)};(self.Astro||(self.Astro={})).visible=l;window.dispatchEvent(new Event(&quot;astro:visible&quot;));})();;(()=&gt;{var A=Object.defineProperty;var g=(i,o,a)=&gt;o in i?A(i,o,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[o]=a;var d=(i,o,a)=&gt;g(i,typeof o!=&quot;symbol&quot;?o+&quot;&quot;:o,a);{let i={0:t=&gt;m(t),1:t=&gt;a(t),2:t=&gt;new RegExp(t),3:t=&gt;new Date(t),4:t=&gt;new Map(a(t)),5:t=&gt;new Set(a(t)),6:t=&gt;BigInt(t),7:t=&gt;new URL(t),8:t=&gt;new Uint8Array(t),9:t=&gt;new Uint16Array(t),10:t=&gt;new Uint32Array(t),11:t=&gt;1/0*t},o=t=&gt;{let[l,e]=t;return l in i?i[l](e):void 0},a=t=&gt;t.map(o),m=t=&gt;typeof t!=&quot;object&quot;||t===null?t:Object.fromEntries(Object.entries(t).map(([l,e])=&gt;[l,o(e)]));class y extends HTMLElement{constructor(){super(...arguments);d(this,&quot;Component&quot;);d(this,&quot;hydrator&quot;);d(this,&quot;hydrate&quot;,async()=&gt;{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest(&quot;astro-island[ssr]&quot;);if(e){e.addEventListener(&quot;astro:hydrate&quot;,this.hydrate,{once:!0});return}let c=this.querySelectorAll(&quot;astro-slot&quot;),n={},h=this.querySelectorAll(&quot;template[data-astro-template]&quot;);for(let r of h){let s=r.closest(this.tagName);s!=null&amp;&amp;s.isSameNode(this)&amp;&amp;(n[r.getAttribute(&quot;data-astro-template&quot;)||&quot;default&quot;]=r.innerHTML,r.remove())}for(let r of c){let s=r.closest(this.tagName);s!=null&amp;&amp;s.isSameNode(this)&amp;&amp;(n[r.getAttribute(&quot;name&quot;)||&quot;default&quot;]=r.innerHTML)}let p;try{p=this.hasAttribute(&quot;props&quot;)?m(JSON.parse(this.getAttribute(&quot;props&quot;))):{}}catch(r){let s=this.getAttribute(&quot;component-url&quot;)||&quot;&lt;unknown&gt;&quot;,v=this.getAttribute(&quot;component-export&quot;);throw v&amp;&amp;(s+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${s}`,this.getAttribute(&quot;props&quot;),r),r}let u;await this.hydrator(this)(this.Component,p,n,{client:this.getAttribute(&quot;client&quot;)}),this.removeAttribute(&quot;ssr&quot;),this.dispatchEvent(new CustomEvent(&quot;astro:hydrate&quot;))});d(this,&quot;unmount&quot;,()=&gt;{this.isConnected||this.dispatchEvent(new CustomEvent(&quot;astro:unmount&quot;))})}disconnectedCallback(){document.removeEventListener(&quot;astro:after-swap&quot;,this.unmount),document.addEventListener(&quot;astro:after-swap&quot;,this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute(&quot;await-children&quot;)||document.readyState===&quot;interactive&quot;||document.readyState===&quot;complete&quot;)this.childrenConnectedCallback();else{let e=()=&gt;{document.removeEventListener(&quot;DOMContentLoaded&quot;,e),c.disconnect(),this.childrenConnectedCallback()},c=new MutationObserver(()=&gt;{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&amp;&amp;this.lastChild.nodeValue===&quot;astro:end&quot;&amp;&amp;(this.lastChild.remove(),e())});c.observe(this,{childList:!0}),document.addEventListener(&quot;DOMContentLoaded&quot;,e)}}async childrenConnectedCallback(){let e=this.getAttribute(&quot;before-hydration-url&quot;);e&amp;&amp;await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute(&quot;opts&quot;)),c=this.getAttribute(&quot;client&quot;);if(Astro[c]===void 0){window.addEventListener(`astro:${c}`,()=&gt;this.start(),{once:!0});return}try{await Astro[c](async()=&gt;{let n=this.getAttribute(&quot;renderer-url&quot;),[h,{default:p}]=await Promise.all([import(this.getAttribute(&quot;component-url&quot;)),n?import(n):()=&gt;()=&gt;{}]),u=this.getAttribute(&quot;component-export&quot;)||&quot;default&quot;;if(!u.includes(&quot;.&quot;))this.Component=h[u];else{this.Component=h;for(let f of u.split(&quot;.&quot;))this.Component=this.Component[f]}return this.hydrator=p,this.hydrate},e,this)}catch(n){console.error(`[astro-island] Error hydrating ${this.getAttribute(&quot;component-url&quot;)}`,n)}}attributeChangedCallback(){this.hydrate()}}d(y,&quot;observedAttributes&quot;,[&quot;props&quot;]),customElements.get(&quot;astro-island&quot;)||customElements.define(&quot;astro-island&quot;,y)}})();&lt;/script&gt;&lt;astro-island uid=&quot;Z1J4djz&quot; prefix=&quot;r0&quot; component-url=&quot;/opt/build/repo/embeds/ClientEmbed.tsx&quot; component-export=&quot;ClientEmbed&quot; renderer-url=&quot;@astrojs/react/client.js&quot; props=&quot;{&amp;quot;providerId&amp;quot;:[0,&amp;quot;youTube&amp;quot;],&amp;quot;wrapperClass&amp;quot;:[0,&amp;quot;aspect-video&amp;quot;],&amp;quot;youTubeId&amp;quot;:[0,&amp;quot;-GYXRopBvW4&amp;quot;]}&quot; ssr=&quot;&quot; client=&quot;visible&quot; before-hydration-url=&quot;astro:scripts/before-hydration.js&quot; opts=&quot;{&amp;quot;name&amp;quot;:&amp;quot;ClientEmbed&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;rootMargin&amp;quot;:&amp;quot;400px&amp;quot;}}&quot; await-children=&quot;&quot;&gt;&lt;div class=&quot;aspect-video&quot;&gt;&lt;div data-testid=&quot;general-observer&quot; class=&quot;mdx-embed&quot;&gt;&lt;div style=&quot;height:0;width:100%&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--astro:end--&gt;&lt;/astro-island&gt; &lt;/div&gt;&lt;h2&gt;The Process &lt;/h2&gt;&lt;p&gt;At Lickability, we staffed a small team to conceive, design, and build a minimal 1.0 and ship it quickly. First, our senior product designer, Sam Gold, spent hours in SwiftUI and Figma prototyping a beautiful and simple UI that lets developers and product managers get the information they need fast and get back to what they do best: making great software. Jillian Meehan, our project manager, broke down Sam’s designs into easily understandable &lt;a href=&quot;https://linear.app&quot;&gt;Linear&lt;/a&gt; tasks that could be delivered in weekly sprints.&lt;/p&gt;&lt;p&gt;Our developers, Brian Capps and Joe Cieplinski, worked together to build out the app and its related widgets and notification extensions over a few months and shipped builds early and often to a rapidly expanding crew of beta testers on TestFlight. We wanted the app to look and feel completely at home on iOS 18, so we built it entirely in SwiftUI with &lt;a href=&quot;/blog/how-to-learn-tca&quot; title=&quot;How to learn The Composable Architecture&quot; data-astro-cid-okzjwv6e&gt;The Composable Architecture&lt;/a&gt;  to keep things well-factored and testable. Plus, if we want to add a watchOS, macOS, or visionOS version in the future, we’ll be able to use the same codebase and easily add more platforms.&lt;/p&gt;&lt;h2&gt;The Details&lt;/h2&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot showing the design details of the transaction detail view&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/804edf229472ec9099c311eaabd7ab10ba69bace-2880x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/804edf229472ec9099c311eaabd7ab10ba69bace-2880x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/804edf229472ec9099c311eaabd7ab10ba69bace-2880x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/804edf229472ec9099c311eaabd7ab10ba69bace-2880x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/804edf229472ec9099c311eaabd7ab10ba69bace-2880x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/804edf229472ec9099c311eaabd7ab10ba69bace-2880x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/804edf229472ec9099c311eaabd7ab10ba69bace-2880x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/804edf229472ec9099c311eaabd7ab10ba69bace-2880x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2880 2880w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/804edf229472ec9099c311eaabd7ab10ba69bace-2880x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1138&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;It wouldn’t be a Lickability app if it didn’t include lots of tiny details that make the experience just that much nicer. We support customizable widgets, notification previews, and deep password autofill integration. Plus, we tint the UI background with the flag of the country where a transaction took place, rotate our globe icon to show the region where the purchase came from, and even apply a subtle parallax effect to our flag artwork as you fidget with your phone.&lt;/p&gt;&lt;p&gt;As we expand the app’s featureset over the coming months, keep an eye out for even more whimsical touches that fit RevenueCat’s style of benevolent chaos. Remember when &lt;a href=&quot;https://x.com/RevenueCat/status/1745494055499288943&quot;&gt;they sent socks to everyone whose app got rejected by Apple&lt;/a&gt;?&lt;/p&gt;&lt;h2&gt;Available Now&lt;/h2&gt;&lt;p&gt;If you’re a RevenueCat customer, &lt;a href=&quot;https://apps.apple.com/us/app/revenuecat-mobile/id6504531798&quot;&gt;download the RevenueCat iOS app&lt;/a&gt; on the App Store today. And if you run an app business that isn’t using RevenueCat yet, sign up and check out the product. If you need help integrating their SDK or building your own app, &lt;a href=&quot;https://lickability.com/contact&quot;&gt;we’d love to help&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;And once you have the app, we’d really appreciate an &lt;a href=&quot;https://www.producthunt.com/posts/official-revenuecat-app-for-ios&quot;&gt;upvote on ProductHunt&lt;/a&gt; or a review on the App Store. It’s been an absolute pleasure working with the cool cats over at RC, and we can’t wait to keep shipping even more features that make our developers happy. 😸&lt;/p&gt; </content:encoded><author>mb bischoff</author></item><item><title>iOS UI Libraries We Love</title><link>https://lickability.com/blog/ui-libraries/</link><guid isPermaLink="true">https://lickability.com/blog/ui-libraries/</guid><description>5 third-party libraries we use all the time</description><pubDate>Thu, 12 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We may be experts at writing apps here at Lickability, but we also know that a lot of really smart people are out there providing excellent reusable software, and we think it would be silly not to rely on their outstanding work. Here are some of our favorite third-party libraries we use to take our apps to the next level.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://movingparts.io/pow&quot;&gt;Pow&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Animation is challenging—even more so when it’s outside the scope of first-party libraries. That’s why we love Pow and end up using it in most of the apps we develop. Pow provides an easy-to-use API for a wide variety of customizable and interoperable animations. Imagine trying to build the animation below from scratch by yourself:&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A gif of a gray heart icon turning red, jumping up and wiggling, and spraying a bunch of smaller heart particles when tapped.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e666d2762c1d65dbea4739007de4483ae9fb58d8-558x558.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/e666d2762c1d65dbea4739007de4483ae9fb58d8-558x558.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/e666d2762c1d65dbea4739007de4483ae9fb58d8-558x558.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=558 558w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e666d2762c1d65dbea4739007de4483ae9fb58d8-558x558.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Using just three lines of code with Pow, it’s incredibly simple to make this heart spray, jump, and wiggle!&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;changeEffect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;spray&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { heart.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;foregroundStyle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(Color.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;red&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) }, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: likes, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;isEnabled&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: isLiked)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;changeEffect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;jump&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;44&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: likes, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;isEnabled&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: isLiked)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;changeEffect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;wiggle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: likes, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;isEnabled&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: isLiked)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;h3&gt;&lt;a href=&quot;https://github.com/marmelroy/PhoneNumberKit&quot;&gt;PhoneNumberKit&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Almost every app that has you make an account deals with phone numbers, and they can get complex. Fortunately, PhoneNumberKit has your back and makes it easy to handle formatting and international localization with built-in country codes and flags. You can either use a completely custom text entry and use their utility functions to format the entry correctly, or use their built-in &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Phone​Number​Text​Field&lt;/code&gt; that handles it all. There’s no need to become an expert on how phone numbers work when they’ve already done the work for you.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://github.com/kean/Pulse&quot;&gt;Pulse&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;At a certain point of complexity, it can be challenging to figure out “what is the app doing right now?”—especially for networking. Pulse is a wonderful tool not only for capturing what’s happening with your &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;URL​Session&lt;/code&gt; requests but also for displaying it in an easy-to-understand way right in the app itself. This lets you dive into a problem immediately when it’s occurring, rather than having to work through console logs that may or may not exist yet. It’s also wonderful when a tester or product owner encounters a problem on their device because Pulse makes it easy to share those logs as well.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the Pulse Mac app showing logs collected using the Pulse SDK, with an iPhone mockup of the Pulse Console integrated into a mobile app.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/4ab63d45d4e186c3c86e33171a705994f27e86d7-2145x1289.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/4ab63d45d4e186c3c86e33171a705994f27e86d7-2145x1289.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/4ab63d45d4e186c3c86e33171a705994f27e86d7-2145x1289.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/4ab63d45d4e186c3c86e33171a705994f27e86d7-2145x1289.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/4ab63d45d4e186c3c86e33171a705994f27e86d7-2145x1289.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/4ab63d45d4e186c3c86e33171a705994f27e86d7-2145x1289.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2145 2145w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/4ab63d45d4e186c3c86e33171a705994f27e86d7-2145x1289.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;961&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Credit: &lt;a href=&quot;https://pulselogger.com/&quot;&gt;Pulse&lt;/a&gt;&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;The interface they provide out of the box is easy to understand, and makes it so much faster to track down what went wrong and when. You can even add your own custom logs that can be displayed intermixed with your network logs, to track down particularly challenging issues.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://github.com/SvenTiigi/WhatsNewKit&quot;&gt;WhatsNewKit&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Sometimes you just have to tell your users what a great job you’ve been doing on the app they love. WhatsNewKit makes it easy to display this information quickly and informatively, while still providing a great deal of customization. It also includes a built-in versioning system to make sure you’re displaying the correct information to the user for the current version of the app.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;An iPhone mockup showing an example of WhatsNewKit&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/34862d7cf7892fe41455d1a65ab35c21d6f06339-2400x1350.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/34862d7cf7892fe41455d1a65ab35c21d6f06339-2400x1350.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/34862d7cf7892fe41455d1a65ab35c21d6f06339-2400x1350.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/34862d7cf7892fe41455d1a65ab35c21d6f06339-2400x1350.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/34862d7cf7892fe41455d1a65ab35c21d6f06339-2400x1350.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/34862d7cf7892fe41455d1a65ab35c21d6f06339-2400x1350.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/34862d7cf7892fe41455d1a65ab35c21d6f06339-2400x1350.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;900&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Credit: &lt;a href=&quot;https://github.com/SvenTiigi/WhatsNewKit&quot;&gt;WhatsNewKit&lt;/a&gt;&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;With WhatsNewKit, you can focus on building new features instead of spending all that time figuring out how to tell users about them.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://github.com/vtourraine/AcknowList&quot;&gt;AcknowList&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Last but certainly not least, here’s a tool for acknowledging the creators who have helped you make the app you wanted to. It’s important to give credit to our helpful third-party developers, and AcknowList helps you do just that by providing an easy way of displaying all those dependencies. Once it’s set up, it will automatically keep itself up to date as well, so you’ll never have to worry about it again.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Three iPhone mockups showing examples of AcknowList in an app&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/1d0ef70ec3563c0b7ded9ac57405e9f8d8cda885-1680x1000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/1d0ef70ec3563c0b7ded9ac57405e9f8d8cda885-1680x1000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/1d0ef70ec3563c0b7ded9ac57405e9f8d8cda885-1680x1000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/1d0ef70ec3563c0b7ded9ac57405e9f8d8cda885-1680x1000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/1d0ef70ec3563c0b7ded9ac57405e9f8d8cda885-1680x1000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1680 1680w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/1d0ef70ec3563c0b7ded9ac57405e9f8d8cda885-1680x1000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;952&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Credit: &lt;a href=&quot;https://github.com/vtourraine/AcknowList&quot;&gt;AcknowList&lt;/a&gt;&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;AcknowList has an easy-to-use API for both UIKit and SwiftUI, so you only have to spend a few minutes to acknowledge the hundreds of hours these wonderful humans have saved you.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;If you’re looking to take your app to the next level with these powerful libraries, reach out to &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Lickability&lt;/a&gt;—we’re here to help you build something amazing.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Andrew Harrison</author></item><item><title>Morphology in Swift</title><link>https://lickability.com/blog/morphology-in-swift/</link><guid isPermaLink="true">https://lickability.com/blog/morphology-in-swift/</guid><description>A guide to automatic numberless pluralization</description><pubDate>Tue, 03 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/morphology&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Morphology&lt;/code&gt;&lt;/a&gt; has been around for a few years now in &lt;code index=&quot;2&quot; isInline=&quot;true&quot;&gt;Foundation&lt;/code&gt;, providing some seriously powerful conveniences for user-facing text, albeit with very little fanfare. With it, we can simplify logic around pluralization and gender as they relate to grammatical agreement, letting the system APIs do the heavy lifting. If you’ve heard of these APIs at all, you’re probably familiar with their headlining, and arguably magical, feature: inflection in interpolated strings.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Produces the following in English depending on the value of `count`:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// - 0:  You have 0 new messages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// - 1:  You have 1 new message&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// - 2+: You have 2 new messages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;You have ^[&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; new message](inflect: true)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;If you’re &lt;em&gt;not&lt;/em&gt; familiar with this, I encourage you to read through Jordan Morgan’s excellent post, &lt;a href=&quot;https://www.swiftjectivec.com/morphology-in-ios-with-automatic-grammar-agreement/&quot;&gt;Morphology in Swift&lt;/a&gt;, to wrap your head around this concise, yet powerful, concept.&lt;/p&gt;&lt;h3&gt;That’s great, but my string doesn’t include a number 🤷‍♂️&lt;/h3&gt;&lt;p&gt;You’ve come to the right place! We similarly struggled with wanting to use this feature without compromising what our designer had in mind for the UI. This is surprisingly difficult to search for, so congratulations on making it here.&lt;/p&gt;&lt;p&gt;When the user receives one or more messages, we want to let them know using natural language &lt;em&gt;without displaying the count&lt;/em&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Depiction of an in-app notification when one or more new messages are received. The first screenshot shows the phrase “New Message,” implying that a single new message was received, while the second one shows the phrase “New Messages,” implying that more than one message was received.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a51bb7dbb931c81b10c33140ae1f81d099107306-2889x577.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/a51bb7dbb931c81b10c33140ae1f81d099107306-2889x577.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/a51bb7dbb931c81b10c33140ae1f81d099107306-2889x577.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/a51bb7dbb931c81b10c33140ae1f81d099107306-2889x577.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/a51bb7dbb931c81b10c33140ae1f81d099107306-2889x577.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/a51bb7dbb931c81b10c33140ae1f81d099107306-2889x577.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/a51bb7dbb931c81b10c33140ae1f81d099107306-2889x577.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/a51bb7dbb931c81b10c33140ae1f81d099107306-2889x577.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2889 2889w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a51bb7dbb931c81b10c33140ae1f81d099107306-2889x577.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;320&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;So is this possible with inflection and string interpolation? Yes… &lt;em&gt;Kinda&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;After scouring the internet, &lt;a href=&quot;https://forums.swift.org/t/medium-is-somehow-translate-to-media-even-though-the-locale-is-default-en-us-and-no-strings-file/54178/10&quot;&gt;picking up hints from forum discussions&lt;/a&gt;, finding bits and pieces in Apple’s documentation (like &lt;a href=&quot;https://developer.apple.com/documentation/foundation/morphology/grammaticalnumber&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Morphology.Grammatical​Number&lt;/code&gt;&lt;/a&gt;), and leveraging the fact that &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Foundation&lt;/code&gt; is open source (see &lt;a href=&quot;https://github.com/apple/swift-corelibs-foundation/blob/main/Sources/Foundation/Morphology.swift&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Morphology.swift&lt;/code&gt;&lt;/a&gt;), we found a working solution:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Result: New Message ✅&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;^[New Message](morphology: { number: &lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;one&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; }, inflect: true)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Result: New Messages ✅&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;^[New Message](morphology: { number: &lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;other&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; }, inflect: true)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;What’s new here is the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;morphology&lt;/code&gt; structure. We haven’t been able to find sufficient documentation, but the aforementioned &lt;a href=&quot;https://forums.swift.org/t/medium-is-somehow-translate-to-media-even-though-the-locale-is-default-en-us-and-no-strings-file/54178/9&quot;&gt;Swift forum discussion&lt;/a&gt; and some &lt;a href=&quot;https://developer.apple.com/documentation/foundation/data_formatting/building_a_localized_food-ordering_app/&quot;&gt;Apple-provided sample code&lt;/a&gt; put us on the right track. After all, if it’s possible to specify a &lt;a href=&quot;https://developer.apple.com/documentation/foundation/morphology/3767103-partofspeech&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;part​Of​Speech&lt;/code&gt;&lt;/a&gt; in this &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;morphology&lt;/code&gt; structure, why not a grammatical &lt;a href=&quot;https://developer.apple.com/documentation/foundation/morphology/3767102-number&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;number&lt;/code&gt;&lt;/a&gt;?&lt;/p&gt;&lt;p&gt;Through trial and error, we attempted to figure out exactly what to specify here. Since &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;part​Of​Speech&lt;/code&gt; works in the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;morphology&lt;/code&gt; structure, we’re reasonably sure we could use &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;number&lt;/code&gt;, as both match their respective property names on &lt;a href=&quot;https://developer.apple.com/documentation/foundation/morphology&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Morphology&lt;/code&gt;&lt;/a&gt; itself. &lt;a href=&quot;https://developer.apple.com/documentation/foundation/morphology/grammaticalnumber&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Grammatical​Number&lt;/code&gt;&lt;/a&gt; has six cases we can choose from:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// From https://github.com/apple/swift-corelibs-foundation/blob/018d8ef5497d030d0bce19b84764012c211d927d/Sources/Foundation/Morphology.swift#L45-L52&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; enum&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; GrammaticalNumber&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Hashable&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Sendable &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;	case&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; singular&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;	case&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; zero&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;	case&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; plural&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;	case&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; pluralTwo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;	case&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; pluralFew&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;	case&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; pluralMany&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;So naturally, we tried to plug these in, using their symbol names directly:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Result: New Message ❌&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;^[New Message](morphology: { number: &lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;plural&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; }, inflect: true)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;But that didn’t work.&lt;/p&gt;&lt;p&gt;Back to digging. If we look at the implementation of &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Morphology.Grammatical​Number&lt;/code&gt;, we can see that it has a &lt;a href=&quot;https://github.com/apple/swift-corelibs-foundation/blob/018d8ef5497d030d0bce19b84764012c211d927d/Sources/Foundation/Morphology.swift#L142-L168&quot;&gt;custom &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Codable&lt;/code&gt; implementation&lt;/a&gt;, specifying &lt;em&gt;other&lt;/em&gt; names for each of these cases:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// From https://github.com/apple/swift-corelibs-foundation/blob/018d8ef5497d030d0bce19b84764012c211d927d/Sources/Foundation/Morphology.swift#L142-L168&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;extension&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Morphology&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.GrammaticalNumber: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Codable &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; init&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; decoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: Decoder) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; container = &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; decoder.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;singleValueContainer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        switch&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; container.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;decode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;one&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:   &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;singular&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;zero&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:  &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;zero&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;other&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;plural&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;two&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:   &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pluralTwo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;few&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:   &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pluralFew&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;many&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:  &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pluralMany&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        default&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;throw&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; CocoaError&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;coderInvalidValue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; encode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; encoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: Encoder) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; container = encoder.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;singleValueContainer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        switch&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;singular&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:   &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; container.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;encode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;one&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;zero&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:       &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; container.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;encode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;zero&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;plural&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:     &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; container.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;encode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;other&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pluralTwo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:  &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; container.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;encode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;two&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pluralFew&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:  &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; container.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;encode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;few&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pluralMany&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; container.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;encode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;many&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Assuming that &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Codable&lt;/code&gt; is used to read and write &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;morphology&lt;/code&gt; (see &lt;a href=&quot;https://developer.apple.com/documentation/foundation/markdowndecodableattributedstringkey&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Markdown​Decodable​Attributed​String​Key&lt;/code&gt;&lt;/a&gt;), we tried again using the encoded representation of &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;plural&lt;/code&gt;, which is &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;&amp;quot;other&amp;quot;&lt;/code&gt;:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Result: New Messages ✅&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;^[New Message](morphology: { number: &lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;other&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; }, inflect: true)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;And that gives us exactly what we want!&lt;/p&gt;&lt;h3&gt;Great, so how should I use this?&lt;/h3&gt;&lt;p&gt;This is where things get a bit complicated and less convenient. Using interpolation to provide the value for &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;number&lt;/code&gt; in the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;morphology&lt;/code&gt; structure is not supported. See the following example:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Result: New Message ❌&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; number = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;other&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;^[New Message](morphology: { number: &lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; }, inflect: true)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;So to extrapolate, we &lt;em&gt;can’t&lt;/em&gt; compute the value to use for &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;number&lt;/code&gt; based on a count value that we know, e.g. &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;let number = (count == 1) ? &amp;quot;one&amp;quot; : &amp;quot;other&amp;quot;&lt;/code&gt;, and then use it with interpolation to produce the desired result. We could interpolate the actual text and use conditional logic based on our known count, which works:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; text = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;New Message&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; count == &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Result: New Message ✅&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;^[&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;](morphology: { number: &lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;one&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; }, inflect: true)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Result: New Messages ✅&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;^[&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;](morphology: { number: &lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;other&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; }, inflect: true)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;But syntactically, that’s far less than ideal as it defeats the purpose of this being a &lt;em&gt;concise&lt;/em&gt; approach — not to mention that we haven’t covered edge cases like -1 in English, which should read singular (irrelevant to our UI, but still), nor have we set ourselves up for success with differing pluralization rules in other languages.&lt;/p&gt;&lt;p&gt;Maybe if we cover all six cases (&lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Morphology.Grammatical​Number&lt;/code&gt;), we can make some nice extensions out of this, starting with converting our known &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Int&lt;/code&gt; to a &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Morphology.Grammatical​Number&lt;/code&gt;, then switching over that instead of using &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;if&lt;/code&gt; statements like we did above.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;extension&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; grammaticalNumberValue: Morphology.GrammaticalNumber {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        switch&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;zero&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;singular&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pluralTwo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pluralFew&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 4&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;max&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pluralMany&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; // Is this even right???&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        default&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;plural&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; // I don’t know???&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Hmm… we have to ask ourselves some tricky questions here. What’s the difference between &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;plural&lt;/code&gt; and &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;plural​Many&lt;/code&gt;? Is &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;plural​Few&lt;/code&gt; actually 3? if you take a look at &lt;a href=&quot;https://developer.apple.com/documentation/foundation/morphology/grammaticalnumber&quot;&gt;Apple’s docs&lt;/a&gt;, they don’t help to disambiguate:&lt;/p&gt;&lt;blockquote&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/morphology/grammaticalnumber/plural&quot;&gt;case plural&lt;/a&gt;&lt;br&gt;&lt;/code&gt;Multiple persons or things, as used for a grammatical number.&lt;br&gt;&lt;br&gt;&lt;code index=&quot;6&quot; isInline=&quot;true&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/morphology/grammaticalnumber/pluralfew&quot;&gt;case plural​Few&lt;/a&gt;&lt;br&gt;&lt;/code&gt;A small number of persons or things, as used for a grammatical number.&lt;br&gt;&lt;br&gt;&lt;code index=&quot;12&quot; isInline=&quot;true&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/morphology/grammaticalnumber/pluralmany&quot;&gt;case plural​Many&lt;/a&gt;&lt;br&gt;&lt;/code&gt;A large number of persons or things, as used for a grammatical number.&lt;/blockquote&gt;&lt;p&gt;“Multiple”, “A small number”, “A large number.” We’d need more precision than that to continue with implementing extensions. After all, we need to supply the right grammatical number associated with any possible &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Int&lt;/code&gt;. If you do a bit of searching around this phrasing (e.g. two, few, many, and other) you might stumble upon The Common Locale Data Repository, or &lt;a href=&quot;https://cldr.unicode.org&quot;&gt;CLDR&lt;/a&gt; for short. &lt;a href=&quot;https://lingohub.com/blog/pluralization#cldr-overview&quot;&gt;Lingohub&lt;/a&gt; provides a nice chart that lists languages along with the values/ranges that belong to each category:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e104b4f1cbbb6b831c3492d72dd3127770c32c8e-1626x1718.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/e104b4f1cbbb6b831c3492d72dd3127770c32c8e-1626x1718.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/e104b4f1cbbb6b831c3492d72dd3127770c32c8e-1626x1718.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/e104b4f1cbbb6b831c3492d72dd3127770c32c8e-1626x1718.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/e104b4f1cbbb6b831c3492d72dd3127770c32c8e-1626x1718.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1626 1626w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e104b4f1cbbb6b831c3492d72dd3127770c32c8e-1626x1718.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1691&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Source: Lingohub, &lt;a href=&quot;https://lingohub.com/blog/pluralization#cldr-overview&quot;&gt;Pluralization (p11n) - the many of plurals&lt;/a&gt;. There are more languages than appear in this screenshot.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;The categories exactly match the cases available on &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Morphology.Grammatical​Number&lt;/code&gt;, so if this is the standard Apple is using, our extension is inherently wrong. “Few” does not necessarily mean “3,” and other categories vary by language (see the official guidelines &lt;a href=&quot;https://cldr.unicode.org/index/cldr-spec/plural-rules#h.m2v80lrdfvr9&quot;&gt;here&lt;/a&gt;). We can see that, suspiciously, English only has values specified for “One” and “Other.” Up to this point, we’ve only used &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;one&lt;/code&gt; and &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;other&lt;/code&gt; for our &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;morphology&lt;/code&gt; &lt;code index=&quot;11&quot; isInline=&quot;true&quot;&gt;number&lt;/code&gt; value. But surely, &lt;code index=&quot;13&quot; isInline=&quot;true&quot;&gt;zero&lt;/code&gt; would work correctly, right?&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Result: New Message ❌&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;^[New Message](morphology: { number: &lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;zero&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; }, inflect: true)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;&lt;em&gt;Wrong&lt;/em&gt;. In English, we’d expect the phrase to read “New Messages.” After all, you’d never say “You have 0 new message.” You’d say “You have 0 new messages.” So while perhaps the chart is right, our result is undesirable. But what about the first example in this post that actually uses the count in the string?&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Result: You have 0 new messages ✅&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; count = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;You have ^[&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; new message](inflect: true)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;It &lt;em&gt;does&lt;/em&gt; work this way. It’s just sadly not the solution to our problem of trying to &lt;em&gt;exclude&lt;/em&gt; the count from the string.&lt;/p&gt;&lt;p&gt;And just to be certain that we haven’t messed anything up syntactically (we are working with strings and stringly typed APIs after all), we can write a fully type-checked expansion of this:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; morphology: Morphology = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; morphology = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Morphology&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    morphology.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;zero&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; morphology&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; string: AttributedString = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; attributedString = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AttributedString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;localized&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;New Message&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    attributedString.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;inflect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;InflectionRule&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;morphology&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: morphology)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; attributedString&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Result: New Message ❌&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(string.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;inflected&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;())&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;But that doesn’t work either.&lt;/p&gt;&lt;p&gt;So rather than “how should I use this?”, perhaps the right question to ask is…&lt;/p&gt;&lt;h3&gt;Should I even use this?&lt;/h3&gt;&lt;p&gt;That depends on your specific conditions. Consider our working conditional example snippet from earlier:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; text = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;New Message&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; count == &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Result: New Message ✅&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;^[&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;](morphology: { number: &lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;one&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; }, inflect: true)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Result: New Messages ✅&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;^[&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;](morphology: { number: &lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;other&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; }, inflect: true)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;We feel comfortable using this only if specific conditions are met and tested:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;We know the user will never have a negative number of messages.&lt;/li&gt;&lt;li&gt;We know that the UI is not shown when there are zero new messages.&lt;/li&gt;&lt;li&gt;We know that this app will only be used in English and possibly other languages that solely utilize the “One” and “Other” CLDR categories.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;So it’s a fairly limited use case. But is it better than the following?&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; count == &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;New Messages&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;	Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;New Message&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Not really. The best we could do is extract this conditional logic of one vs. other to an extension, provided that all use cases of our new API meet the general conditions discussed above.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;extension&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    init&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;countToInflect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; countToInflect == &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;^[&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;](morphology: { number: &lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;one&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; }, inflect: true)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        } &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;^[&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;](morphology: { number: &lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;other&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; }, inflect: true)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Then, future use cases can be simplified:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Result: New Messages ✅&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;New Message&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;countToInflect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Result: New Message ✅&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;New Message&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;countToInflect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Result: New Messages ✅&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;New Message&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;countToInflect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;But again, we want to make sure this is only used in situations where our very limited conditions are met. Extracting it to an easy-to-use extension runs the risk of someone using the extension without fully understanding its limitations and implications. If you choose to go this route, be sure to provide clear documentation.&lt;/p&gt;&lt;h3&gt;Other considerations and disclaimers&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;While we can’t find specific documentation pointing to the encoded &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;morphology&lt;/code&gt; structure to support the use case here, we don’t believe it to be considered usage of undocumented APIs in the “reject your app” kind of way. It’s just not documented &lt;em&gt;well enough&lt;/em&gt; to infer the behavior discussed in this post. Attributes like &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;inflect&lt;/code&gt; are commonly used in shipping apps and appear in WWDC sessions, and &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;morphology: { part​Of​Speech: \&amp;quot;noun\&amp;quot; }&lt;/code&gt; appears in &lt;a href=&quot;https://developer.apple.com/documentation/foundation/data_formatting/building_a_localized_food-ordering_app/&quot;&gt;Apple’s sample code&lt;/a&gt;. You can even create your own attributes to use in markdown syntax using &lt;a href=&quot;https://developer.apple.com/documentation/foundation/markdowndecodableattributedstringkey&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Markdown​Decodable​Attributed​String​Key&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;While the behavior seen when using the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Morphology.Grammatical​Number.zero&lt;/code&gt; was initially unexpected, given what we learned about CLDR, we don’t think this is a bug.&lt;/li&gt;&lt;li&gt;These morphology features are not supported in every language. Initially, only English and Spanish were supported. According to &lt;a href=&quot;https://developer.apple.com/wwdc23/10153&quot;&gt;WWDC23: Unlock the power of grammatical agreement&lt;/a&gt;, the list of supported locales now also includes French, Italian, Brazilian Portuguese, European Portuguese, and German. If your app supports only a subset of these, and you put in adequate testing time, you can consider using this solution.&lt;/li&gt;&lt;li&gt;Automatic grammar agreement isn’t the only solution to pluralizing strings. &lt;a href=&quot;https://developer.apple.com/documentation/xcode/localizing-and-varying-text-with-a-string-catalog#Add-pluralizations&quot;&gt;String catalogs&lt;/a&gt; provide a robust solution for pluralization that’s compatible with far more languages.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;In conclusion…&lt;/h3&gt;&lt;p&gt;While this specific use case isn’t something we’ll reach for in every project, it’s always fun to have a hunch that something &lt;em&gt;should&lt;/em&gt; be possible, dive deep down into the rabbit hole, and come out learning a whole lot more than you bargained for. Morphology in Swift is a complicated topic that’s made fairly easy to use through nice APIs. In the coming years, we hope to see more language support and improved documentation to reduce the number of caveats to consider before leaning on this powerful solution.&lt;/p&gt;&lt;h3&gt;TL;DR&lt;/h3&gt;&lt;blockquote&gt;Is it possible to achieve automatic pluralization in Swift &lt;em&gt;without&lt;/em&gt; a number appearing in the string?&lt;/blockquote&gt;&lt;p&gt;Yes&lt;/p&gt;&lt;blockquote&gt;Is it concise to do so?&lt;/blockquote&gt;&lt;p&gt;Fairly:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;^[&amp;#x3C;#string literal or interpolated string here#&gt;](morphology: { number: &lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&amp;#x3C;#`Codable` string representation of a `Morphology.GrammaticalNumber` here#&gt;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; }, inflect: true)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// For example…&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Result: Dogs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;^[Dog](morphology: { number: &lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;other&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; }, inflect: true)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;blockquote&gt;Does it work in every language?&lt;/blockquote&gt;&lt;p&gt;No&lt;/p&gt;&lt;blockquote&gt;Should I use it?&lt;/blockquote&gt;&lt;p&gt;Only in carefully considered circumstances, and provided that the code savings are worth it to you.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Need more Swift help? That‘s one of our specialties! &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Get in touch&lt;/a&gt; if you have a mobile app that could use our expertise.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Michael Liberatore</author></item><item><title>The New Lickability</title><link>https://lickability.com/blog/the-new-lickability/</link><guid isPermaLink="true">https://lickability.com/blog/the-new-lickability/</guid><description>A fresh face for our studio</description><pubDate>Thu, 15 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Lickability has evolved a lot over the &lt;a href=&quot;https://lickability.com/blog/15-years/&quot;&gt;past 15 years&lt;/a&gt;. What started as a group of three friends grew into a company of great folks making apps that we’re proud of and that reach millions of people worldwide.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A wall decal of the old logo in our office next to a screenshot of the old logo on our website&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e3d73bbe04f362be93f67370f49d3780333a03c9-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/e3d73bbe04f362be93f67370f49d3780333a03c9-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/e3d73bbe04f362be93f67370f49d3780333a03c9-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/e3d73bbe04f362be93f67370f49d3780333a03c9-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/e3d73bbe04f362be93f67370f49d3780333a03c9-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/e3d73bbe04f362be93f67370f49d3780333a03c9-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/e3d73bbe04f362be93f67370f49d3780333a03c9-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/e3d73bbe04f362be93f67370f49d3780333a03c9-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e3d73bbe04f362be93f67370f49d3780333a03c9-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;889&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Our old logo, with the tried and true cursive “L”&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;In that time, the Lickability brand has grown alongside us — that classic cursive “L” accompanied us to &lt;a href=&quot;https://lickability.com/blog/inside-lickability-hq/&quot;&gt;different&lt;/a&gt; &lt;a href=&quot;https://lickability.com/blog/our-new-office-is-official/&quot;&gt;offices&lt;/a&gt; and &lt;a href=&quot;https://lickability.com/blog/new-year-new-site/&quot;&gt;website iterations&lt;/a&gt;, and adorned plenty of business cards, pins, stickers, and hoodies. We even put it on a custom ice cube stamp for company holiday parties! It has served us extremely well.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A bento-box-style gallery of images, including: an ice cube with the cursive Lickability “L” logo stamped into it, a grey hoodie with the logo in white, a red enamel pin with the logo, and a pizza with a cursive L drawn on it in ricotta&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/7d57589195cdcda7a4fb6b80144906b330dda475-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/7d57589195cdcda7a4fb6b80144906b330dda475-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/7d57589195cdcda7a4fb6b80144906b330dda475-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/7d57589195cdcda7a4fb6b80144906b330dda475-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/7d57589195cdcda7a4fb6b80144906b330dda475-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/7d57589195cdcda7a4fb6b80144906b330dda475-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/7d57589195cdcda7a4fb6b80144906b330dda475-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/7d57589195cdcda7a4fb6b80144906b330dda475-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/7d57589195cdcda7a4fb6b80144906b330dda475-3656x2032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;889&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;The circle “L” was everywhere&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;The Lickability of today is not the same Lickability that existed 15 years ago. The landscape of our industry has changed, and we’ve changed along with it. When iOS apps were just starting to take center stage, we carved out a niche for ourselves, crafting apps that stand shoulder-to-shoulder with the industry’s best. Now, our clients are looking toward us to help with more than apps — we shape design systems, build backends, and ship cross-platform software.&lt;/p&gt;&lt;p&gt;To compliment our evolution, it’s time for our brand to evolve, too. A little less than a year ago, we decided to start the search for the next version of Lickability. Today, it is &lt;em&gt;officially&lt;/em&gt; here — it’s real, we love it, we’re excited, and we’re ready to share it.&lt;/p&gt;&lt;h3&gt;The beginning&lt;/h3&gt;&lt;p&gt;You may have seen the new logo somewhere already, and you’re probably reading this on our redesigned website. So while you get better acquainted with our new look, let’s jump back a few months to talk about the journey we took to get here.&lt;/p&gt;&lt;p&gt;It started, as many things do, with a Notion document. We put together a “Lickability Brand Brief” to use as a roadmap of where we wanted to go. It came in handy when we started reaching out to branding agencies to find the perfect partner.&lt;/p&gt;&lt;p&gt;We talked to lots of folks — we told them our story and explained what we were looking for — before meeting &lt;a href=&quot;https://www.radialinear.studio/&quot;&gt;Radialinear&lt;/a&gt;, the wonderful Chicago-based studio that we ended up working with. They got to know us over a handful of weeks, helped us figure out who we wanted to be, and walked us through many possible versions of our new visual identity. We were lucky to partner with people who understood us so well.&lt;/p&gt;&lt;h3&gt;The process&lt;/h3&gt;&lt;p&gt;Working with a branding agency was pretty eye-opening! We knew we wanted to do a “brand refresh,” and we had our story packaged up in a nice little document, but it turns out it’s hard to distill 15 years of company history into the essence of who we are and how we want to present ourselves to the world.&lt;/p&gt;&lt;p&gt;Luckily, we were in good hands with Radialinear. We started with a “brand discovery” phase, where we got the whole team together for some collaborative exercises to help us figure out what “Lickability” means to each of us. We went into this project thinking we already knew our brand well, but we quickly learned that there was so much more to think about. This was a crucial first step, and it turned out to be a lot of fun.&lt;/p&gt;&lt;p&gt;After doing some more behind-the-scenes research to learn about our company, they helped us nail down our brand strategy &amp;amp; identity — our mission, our personality, and our core values. Then we were ready to get to the &lt;em&gt;real&lt;/em&gt; fun part: the visuals.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A moodboard of reference images that we used as inspiration for our brand refresh&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/11eceed7b84cb340a189a663e0f34d41d4123d34-3872x1741.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/11eceed7b84cb340a189a663e0f34d41d4123d34-3872x1741.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/11eceed7b84cb340a189a663e0f34d41d4123d34-3872x1741.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/11eceed7b84cb340a189a663e0f34d41d4123d34-3872x1741.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/11eceed7b84cb340a189a663e0f34d41d4123d34-3872x1741.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/11eceed7b84cb340a189a663e0f34d41d4123d34-3872x1741.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/11eceed7b84cb340a189a663e0f34d41d4123d34-3872x1741.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/11eceed7b84cb340a189a663e0f34d41d4123d34-3872x1741.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/11eceed7b84cb340a189a663e0f34d41d4123d34-3872x1741.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;719&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;The final moodboard&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Three mockups of subway ad posters showing in-progress brand concepts&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c54a0d154589b55ca18b8ae9ac77bbd3d28f3571-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/c54a0d154589b55ca18b8ae9ac77bbd3d28f3571-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/c54a0d154589b55ca18b8ae9ac77bbd3d28f3571-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/c54a0d154589b55ca18b8ae9ac77bbd3d28f3571-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/c54a0d154589b55ca18b8ae9ac77bbd3d28f3571-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/c54a0d154589b55ca18b8ae9ac77bbd3d28f3571-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/c54a0d154589b55ca18b8ae9ac77bbd3d28f3571-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/c54a0d154589b55ca18b8ae9ac77bbd3d28f3571-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c54a0d154589b55ca18b8ae9ac77bbd3d28f3571-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;900&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Some early concepts&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;We started by reviewing moodboards from Radialinear, pointing out the things we liked and didn’t like until we had a solid base of inspiration to draw from, including everything from 2000s Mac OS X to 1960s peace posters. From there, we spent a few weeks trying out different visual directions — shapes and colors, logos, typography, etc. — until all of the work finally culminated in a finished product. We found something that felt &lt;em&gt;right.&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;The final form&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;This is the new Lickability.&lt;/strong&gt; Except, not really — we are the same company as we were a year ago. But now we know ourselves a little better, we’re more confident in what we’re doing, and we have a shiny new logo to put on our business cards and stickers. (Time to order a new custom ice stamp!)&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Our new logo: the word Lickability with a glyph to the left of it. The glyph is a smiling mouth with a tongue sticking out, with a semicircle of lines above it that look like a buffering or lightbulb symbol.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/3fafef52f6f7e11e22cac444f360dcec673f7fcb-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/3fafef52f6f7e11e22cac444f360dcec673f7fcb-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/3fafef52f6f7e11e22cac444f360dcec673f7fcb-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/3fafef52f6f7e11e22cac444f360dcec673f7fcb-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/3fafef52f6f7e11e22cac444f360dcec673f7fcb-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/3fafef52f6f7e11e22cac444f360dcec673f7fcb-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/3fafef52f6f7e11e22cac444f360dcec673f7fcb-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/3fafef52f6f7e11e22cac444f360dcec673f7fcb-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/3fafef52f6f7e11e22cac444f360dcec673f7fcb-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;900&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Our new logo&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A slanted grid of different colored squares that each have our new tongue glyph in them&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/940107f0452499da582bc827bdc8cead60b736d5-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/940107f0452499da582bc827bdc8cead60b736d5-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/940107f0452499da582bc827bdc8cead60b736d5-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/940107f0452499da582bc827bdc8cead60b736d5-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/940107f0452499da582bc827bdc8cead60b736d5-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/940107f0452499da582bc827bdc8cead60b736d5-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/940107f0452499da582bc827bdc8cead60b736d5-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/940107f0452499da582bc827bdc8cead60b736d5-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/940107f0452499da582bc827bdc8cead60b736d5-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;900&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Our new glyph&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;We’re very glad we worked with Radialinear on this project. They made the whole process enjoyable, and we couldn’t be happier with the end result. Their founder, Amy, also left us with some nice parting words:&lt;/p&gt;&lt;blockquote class=&quot;quote-block&quot;&gt; &lt;p class=&quot;quote-text&quot;&gt;From our first conversation with the Lickability team, it was apparent that they care deeply about the people who interact with the apps they build. This passion, paired with their craft and playfulness, fueled a truly joyful brand design system. It also inspired their brand positioning statement: “People who love to build apps that people love.” This focus on the humanity behind digital products already set Lickability apart from the rest as a company, and now their brand reflects that.&lt;/p&gt;&lt;p class=&quot;quote-text&quot;&gt;Lickability is such a fun name, and inspired a lot of logomark explorations. The final logomark speaks to the name and its origins in a famous Steve Job quote, while also referencing progress, iteration, and app loading screens. The mark also balances the duality of Lickability – detail-oriented experts who are full of personality.&lt;/p&gt;&lt;p class=&quot;quote-text&quot;&gt;It’s not often we make a brand system that we wish we could steal for ourselves. We’re immensely proud of this work, and so thrilled to have partnered with the wonderful humans at Lickability.&lt;/p&gt; &lt;a href=&quot;https://www.radialinear.studio/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; &lt;cite class=&quot;quote-author&quot;&gt;Amy Schwartz, Radialinear&lt;/cite&gt; &lt;/a&gt; &lt;/blockquote&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Our typography guidelines, showing examples of which fonts we use for different purposes&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a17a8e07634ab6e9034a991fc41d5f21adae21cf-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/a17a8e07634ab6e9034a991fc41d5f21adae21cf-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/a17a8e07634ab6e9034a991fc41d5f21adae21cf-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/a17a8e07634ab6e9034a991fc41d5f21adae21cf-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/a17a8e07634ab6e9034a991fc41d5f21adae21cf-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/a17a8e07634ab6e9034a991fc41d5f21adae21cf-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/a17a8e07634ab6e9034a991fc41d5f21adae21cf-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/a17a8e07634ab6e9034a991fc41d5f21adae21cf-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a17a8e07634ab6e9034a991fc41d5f21adae21cf-3840x2160.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;900&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Our new typography guidelines, featuring &lt;a href=&quot;https://displaay.net/typeface/reckless-collection/reckless-neue/&quot;&gt;Reckless Neue&lt;/a&gt; and &lt;a href=&quot;https://abcdinamo.com/typefaces/diatype&quot;&gt;ABC Diatype&lt;/a&gt;&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A grid of solid colors from our new color palette&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a4107c43e6047647fc035ba64a7e416495852ebf-1920x1080.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/a4107c43e6047647fc035ba64a7e416495852ebf-1920x1080.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/a4107c43e6047647fc035ba64a7e416495852ebf-1920x1080.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/a4107c43e6047647fc035ba64a7e416495852ebf-1920x1080.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/a4107c43e6047647fc035ba64a7e416495852ebf-1920x1080.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1920 1920w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a4107c43e6047647fc035ba64a7e416495852ebf-1920x1080.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;900&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Our new color palette&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;In the months since they handed us the deliverables, we’ve been building up our brand assets in the shadows, not yet ready to step out into the light and show off our new look. Turns out, rolling out a rebrand is not quite as simple as slapping a different logo on some stuff and calling it a day — there was a lot of work that still had to be done.&lt;/p&gt;&lt;p&gt;The &lt;em&gt;first&lt;/em&gt; thing we did was change our Slack workspace icon. That’s how you know it’s serious. We also ordered a batch of new business cards for everyone, got some new pins and stickers to give out at conferences, and designed some new shirts for our Cotton Bureau store. If you have any of our old merch, congratulations — that’s a rare, vintage collectible now.&lt;/p&gt;&lt;p&gt;Redesigning our website has been the biggest part of the project. We’ll save the fun details for another day, but we’re happy to officially present the new &lt;a href=&quot;http://lickability.com/&quot;&gt;Lickability.com&lt;/a&gt;! It’s been a real labor of love over the past few months, and it feels great to finally be able to show it off.&lt;/p&gt;&lt;h3&gt;The future&lt;/h3&gt;&lt;p&gt;We spent nearly a year figuring out how to evolve our brand into something that represents where we’ve been and where we‘re headed, and we’re proud of the result. Changing a logo that’s been with you for a decade is scary, and digging deep to refine the principles you use to do your work every day is even scarier. But it’s also really rewarding to get it right.&lt;/p&gt;&lt;p&gt;Lickability will continue evolving — it’s impossible not to when you have the chance to work alongside so many brilliant, passionate people. So, as we grow, we hope that this brand identity will grow with us as we tackle new, challenging projects and ship even better software.&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Our WWDC24 wish list</title><link>https://lickability.com/blog/our-wwdc24-wish-list/</link><guid isPermaLink="true">https://lickability.com/blog/our-wwdc24-wish-list/</guid><description>What we’re hoping for at this year’s Developer Conference</description><pubDate>Thu, 06 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We shared our &lt;a href=&quot;https://lickability.com/blog/our-wwdc23-hopes-and-dreams/&quot;&gt;hopes and dreams&lt;/a&gt; with you before Apple’s developer conference last year, and now we’re back with a whole bunch more. Fingers crossed that all of our wishes will come true next week! 🤞&lt;/p&gt;&lt;h2&gt;Sam Gold&lt;/h2&gt;&lt;p&gt;There comes a time in every idea prototyping cycle when you face the facts and have to switch a SwiftUI List to a LazyVStack because List just won‘t cut it anymore.&lt;/p&gt;&lt;p&gt;So you either forge deeper and design around the limits of List, introspect some undesirable behavior away, or give up on List entirely and use some other container. Which is a shame; there are so many benefits to using a List when something is semantically just a List. Deletion controls, accessibility hints, swipe actions, headings, etc.&lt;br&gt;So my wish is full design control over Lists. No more goofy header size hacks, no more automatic implicit padding you can‘t get rid of, bridge the gap with the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;.on​Delete&lt;/code&gt; modifier adding a delete accessory but not having an &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;.on​Insert&lt;/code&gt; for the green add button accessory. I should be able to use any stock UITableViewCell accessory from SwiftUI.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Oh, I forgot one thing:&lt;/strong&gt; Siri default apps. I would love to just use Siri without having to stipulate “remind me to do something in Things” or “play this album on Spotify”.&lt;/p&gt;&lt;h2&gt;&lt;a href=&quot;https://bento.me/td&quot;&gt;Tom DeVuono&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I’d like to see 3rd party watch faces, blood pressure readings for watchOS, and improved green bubble texts.&lt;/p&gt;&lt;h2&gt;&lt;a href=&quot;https://mbbischoff.com/&quot;&gt;mb&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I’m hoping that this year’s WWDC brings thoughtful integration of LLMs across Apple’s OSes, performance and reliability updates for core serves, and a few power-user tweaks I’ll use every day.&lt;/p&gt;&lt;p&gt;On iOS and iPadOS, my wishlist includes a Home screen redesign, customizable Lock Screen quick actions, and better notification management. I’m also looking forward to potential improvements in macOS, like a notifications redesign and Live Activities, as well as custom watch faces on watchOS.&lt;/p&gt;&lt;p&gt;For developers, I’m excited about the possibility of Xcode for iPad, a Copilot-like assistant in Xcode, and API access for local and cloud LLMs.&lt;br&gt;For the my full wishlist, check my &lt;a href=&quot;https://mbbischoff.com/wwdc-2024-wishlist/&quot;&gt;blog post&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;Michael Liberatore&lt;/h2&gt;&lt;p&gt;This year, in addition to the hundreds of API treats I haven’t even thought of, I’m hoping for some solid developer quality-of-life additions.&lt;/p&gt;&lt;p&gt;At the top of my wishlist is integrated device mirroring in Xcode. These days, I do a lot of native Android development in addition to iOS, and one huge boost to my productivity is using &lt;a href=&quot;https://developer.android.com/studio/run/device&quot;&gt;Android Studio’s device mirroring feature&lt;/a&gt;. Don’t get me wrong, the iOS simulator is a godsend. But there are so many things it understandably can’t naturally do, requiring physical hardware (e.g. taking photos, tracking health metrics, etc). Android’s device mirroring allows you to always have your device’s screen streamed to your IDE, BUT! you can also interact directly with it using your mouse and keyboard, just like a simulator. I was blown away the first time I got to use this feature, and I think it’d make a great addition to iOS development workflows.&lt;/p&gt;&lt;p&gt;Like others, I’m also hoping for a first-party Copilot-like LLM integration in Xcode to speed up routine coding tasks and suggest approaches. I’m very curious to see if and how Apple aims to provide comparable results to competitors, but with a focus on privacy and reduced energy consumption.&lt;/p&gt;&lt;p&gt;Lastly, and &lt;a href=&quot;https://lickability.com/blog/our-wwdc23-hopes-and-dreams/&quot;&gt;not to sound like a broken record&lt;/a&gt;, I’d LOVE for stale, outdated errors in Xcode to be a problem of the past. Everyday I spend at least a minute questioning whether the errors in my issue navigator are real, or a fragment of former lines of code, and I sincerely hope those days will be behind us soon.&lt;/p&gt;&lt;h2&gt;&lt;a href=&quot;http://jillian.cloud/&quot;&gt;Jillian Meehan&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I just want one thing this year: an actual “Now Playing” Home Screen widget. If you can show me whatever I’m listening to in other places — on the Lock Screen, on my watch, in Control Center on my Mac — then it shouldn’t be too hard to put that in a nice little widget, right?&lt;/p&gt;&lt;p&gt;More widgets in general is always my wish — how about some more widgets for Apple Health? Or the new &lt;a href=&quot;https://lickability.com/blog/apple-sports/&quot;&gt;Sports&lt;/a&gt; app?&lt;/p&gt;&lt;h2&gt;Michael Amundsen&lt;/h2&gt;&lt;p&gt;I really want to see SwiftUI APIs have a much parity as possible with their underlying UIKit types. If the underlying type has something exposed like say &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;content​Offest&lt;/code&gt;, I should be able to easily access that in a SwiftUI List. That is a huge undertaking so even better yet, open source SwiftUI in the way they did with Swift so the community can help expand, fix, and improve upon it.&lt;/p&gt;&lt;p&gt;As a developer, make it so an iPad is relevant to my workflow. Ideally, I’d love for it to be viable to take an iPad into another room or place instead of my laptop and be able to continue my workflow.&lt;/p&gt;&lt;p&gt;I’ve been spending a lot of time in Android Studio as of late and it make going back to Xcode in some key areas difficult. The ability to integrate AI chat bots and suggestions both first party and third party would help a lot. Device mirroring and ability to control a physical device inside of Xcode like a simulator would be huge.&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Your app doesn&apos;t need a chat feature</title><link>https://lickability.com/blog/your-app-doesnt-need-a-chat-feature/</link><guid isPermaLink="true">https://lickability.com/blog/your-app-doesnt-need-a-chat-feature/</guid><description>Your app doesn&apos;t need a chat feature</description><pubDate>Tue, 07 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At Lickability, we get a dozen or so pitches for app ideas per week. I’ve noticed a worrying trend emerging throughout a fair number of these pitches: &lt;strong&gt;a chat feature&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;On its face, it’s an obvious fix to whatever issue you’re running into. Trying to increase user engagement? Let users chat with each other inside the app. Community building? Mega group chats. Support and feedback channels? Sounds like a chatbot.&lt;/p&gt;&lt;p&gt;The cracks begin to show when rubber hits the road and we start thinking about things critically.&lt;/p&gt;&lt;h3&gt;Chat is Noisy&lt;/h3&gt;&lt;p&gt;At the very highest level, the core issue with adding a chat feature is that it dilutes the potency of your app. What makes an app sticky at launch is being &lt;strong&gt;really&lt;/strong&gt; good at a very specific thing — not an app that immediately covers every possible base.&lt;/p&gt;&lt;p&gt;Imagine if Instagram, in its infancy in 2010, had tried to pack in everything from day one: messaging, photo sharing, video, stories, shopping, and live streaming. It would have sunk.&lt;/p&gt;&lt;p&gt;Instead, they started with a simple killer feature—transforming lousy cell phone photos into share-worthy images. The rest was drip-fed over 15 years. Instagram DMs didn’t even appear until four years post-launch.&lt;/p&gt;&lt;p&gt;Start with your core idea. Avoid drowning it in feature overload.&lt;/p&gt;&lt;h3&gt;Chat is Insular&lt;/h3&gt;&lt;p&gt;This may be obvious, but it’s worth stating: when you create an insular chat feature, you limit who you can chat with to only other users.&lt;/p&gt;&lt;p&gt;If one of your users finds something in your app that they really want to share with a friend, they first need to persuade them to download the app, sign up, possibly follow each other, and then share the content. At each step, the likelihood of the friend reaching the finish line drops by 15%.&lt;/p&gt;&lt;p&gt;But what’s almost 100% guaranteed is that if someone wants to share content in your app with a friend, they probably have that friend’s number or Messenger or WhatsApp or Telegram or Signal or Discord or Slack or Snapchat. And all of those apps support rich links. Engineering time is better spent getting &lt;em&gt;that&lt;/em&gt; experience frictionless and great — meet potential users where they’re at.&lt;/p&gt;&lt;h3&gt;Chat is Expensive&lt;/h3&gt;&lt;p&gt;If your chat feature isn’t about sharing content, there’s a good chance there’s already a product or company out there that exists solely for your type of use case. For instance, if you need users to chat with support, &lt;a href=&quot;https://www.apple.com/ios/business-chat/&quot;&gt;Business Chat&lt;/a&gt; might be a good option. And it’ll almost certainly be &lt;strong&gt;magnitudes less expensive&lt;/strong&gt; than building a bespoke chat feature.&lt;/p&gt;&lt;p&gt;I fully recognize this is counter to our bottomline as an app development agency, but removing a chat feature will save so much in your budget that can be better spent on things that your users would appreciate more.&lt;/p&gt;&lt;p&gt;For instance, &lt;a href=&quot;https://lickability.com/blog/plussing-your-ios-app/&quot;&gt;plussing your app&lt;/a&gt; to make it a joy to use and have it work seamlessly with the system. Or adding first-class support for rich link previews like I mentioned above.&lt;/p&gt;&lt;h3&gt;Chat is Messy&lt;/h3&gt;&lt;h4&gt;Scaling&lt;/h4&gt;&lt;p&gt;As your app (hopefully) grows, you will need to scale the chat infrastructure up and up and up. And it’s not as trivial as throwing a more expensive AWS machine at it — there’s a reason why Meta has &lt;em&gt;hundreds&lt;/em&gt; of people working solely on Messenger. Chat has a bunch of hard problems intrinsic to the nature of the product. Why would you want to be restrained by those problems if chat is just an afterthought?&lt;/p&gt;&lt;h4&gt;Baggage&lt;/h4&gt;&lt;p&gt;When you add a chat feature, you immediately take on the intrinsic baggage that comes with it. Where are you going to store messages? What about the attachments that could be orders of magnitude larger than a text message? How are you going to protect them? How are you moderating user-generated content for inappropriate behavior?&lt;/p&gt;&lt;p&gt;It’s a lot of heavy lifting for something that might not even align with your app‘s core value.&lt;/p&gt;&lt;h4&gt;User expectations&lt;/h4&gt;&lt;p&gt;Chat apps have gained a lot of features recently that users expect to be standard — reactions, in-line replies and threading, attachments, rich text. These features aren’t intrinsic, and a chat feature without those might end up feeling like a 2000s AIM client.&lt;/p&gt;&lt;h3&gt;We have enough chat&lt;/h3&gt;&lt;p&gt;It’s 2024. We have accounts everywhere. There are probably 100 apps on your phone, a good chunk of which can send notifications.&lt;/p&gt;&lt;p&gt;I wake up every morning and check Bluesky, Discord, Instagram, Mastodon, LinkedIn, iMessage, Snapchat, Threads, X, TikTok, Slack, my email, my Figma notifications, and my todo list inbox.&lt;/p&gt;&lt;p&gt;Your app‘s biggest draw might just be offering a sanctuary from notification overload, and offering a hyper-focused experience. Something that distills your secret sauce to its purest ingredients.&lt;/p&gt;&lt;p&gt;With all this in mind, these points are just guidelines. Convinced your chat feature breaks the mold? I’m all ears. &lt;a href=&quot;http://lickability.com/contact&quot;&gt;Reach out&lt;/a&gt;, and let’s see what we can build together.&lt;/p&gt; </content:encoded><author>Sam Henri Gold</author></item><item><title>How to learn The Composable Architecture</title><link>https://lickability.com/blog/how-to-learn-tca/</link><guid isPermaLink="true">https://lickability.com/blog/how-to-learn-tca/</guid><description>Our tips for getting started with TCA from Pointfree</description><pubDate>Thu, 18 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you’re like us, it can feel a little daunting to pick up a new software architecture, especially once you’ve learned the ins and outs of another one already! Even if you aren’t planning on switching to or starting your app with a new architecture, it can be really beneficial to check out new ways of doing things. You never know what you might learn that might help!&lt;/p&gt;&lt;p&gt;This post is not an attempt to teach you how to use &lt;a href=&quot;https://www.pointfree.co/collections/composable-architecture&quot;&gt;The Composable Architecture&lt;/a&gt; (TCA). Instead, we’re going to provide a good path for how to get started with learning the framework. The fine folks at &lt;a href=&quot;https://www.pointfree.co/&quot;&gt;Point-Free&lt;/a&gt; have done an excellent job with providing resources, but we know it can feel overwhelming to jump into something for the first time and not know where to start.&lt;/p&gt;&lt;p&gt;First, in order to use something, you need to know what it is! Fortunately, that’s answered pretty readily in the &lt;a href=&quot;https://github.com/pointfreeco/swift-composable-architecture/blob/main/README.md&quot;&gt;Readme&lt;/a&gt; of the &lt;a href=&quot;https://github.com/pointfreeco/swift-composable-architecture&quot;&gt;Swift Composable Architecture&lt;/a&gt; GitHub page. Not only does it clearly define the &lt;em&gt;What&lt;/em&gt; and &lt;em&gt;Why&lt;/em&gt; of TCA, it provides a large number of examples, documentation, and other resources to help you really understand how to accomplish your goal of making a well-engineered app. We recommend at least reading the &lt;a href=&quot;https://github.com/pointfreeco/swift-composable-architecture?tab=readme-ov-file#what-is-the-composable-architecture&quot;&gt;“&lt;strong&gt;What is the Composable Architecture?”&lt;/strong&gt;&lt;/a&gt; section, and skimming through the rest to familiarize yourself with some terminology.&lt;/p&gt;&lt;p&gt;Next, if you’re looking for a practical way to start learning TCA, follow this great &lt;a href=&quot;https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/1.23.1/documentation/composablearchitecture/gettingstarted&quot;&gt;Getting Started&lt;/a&gt; guide. It walks you through the basics of building and testing a feature in an easily digestible manner. We find practical implementation of concepts to be a great way to solidify those ideas after reading. This guide not only illustrates the key concepts but also explains how testing works in TCA, which is a core benefit of this architecture.&lt;/p&gt;&lt;p&gt;Some of the concepts and naming standards are pretty different than what you might be used to from Apple tutorials on Swift or SwiftUI, like &lt;a href=&quot;https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/1.23.1/documentation/composablearchitecture/reducer/&quot;&gt;Reducer&lt;/a&gt; or &lt;a href=&quot;https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/1.23.1/documentation/composablearchitecture/effect&quot;&gt;Effect&lt;/a&gt;. It’s a good idea to take time to understand them in detail. Now that you’ve had a chance to learn and use some of those concepts already, we recommend following along with the first chapter of &lt;a href=&quot;https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/1.23.1/tutorials/meetcomposablearchitecture/&quot;&gt;tutorials&lt;/a&gt; provided by Point-Free. It’s a great way to reinforce the ideas you’ve learned about, to make sure you understand the foundations you’re going to build upon.&lt;/p&gt;&lt;p&gt;Next is a &lt;a href=&quot;https://www.pointfree.co/collections/composable-architecture/composable-architecture-1-0&quot;&gt;video series&lt;/a&gt; called “A tour of the Composable Architecture,” which will give you a look at a number of important concepts through the lens of TCA, like navigation and persistence. This will be invaluable as you expand beyond the simple examples from the previous steps, and also provides the information in a different, more visual format if you’re not a text-based learner. Much of the video content is subscriber-only, but you can watch one video for free to get a taste — and we highly recommend subscribing if you’ve made it this far!&lt;/p&gt;&lt;p&gt;Finally, return to the interactive &lt;a href=&quot;https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/1.23.1/tutorials/meetcomposablearchitecture/&quot;&gt;tutorials&lt;/a&gt;, focusing on Chapter 2 this time. This is a more in-depth look at navigation, and gives you insight into how to start handling more complex interactions between features and screens. This will help solidify a lot of what you’ve learned from previous sections.&lt;/p&gt;&lt;h3&gt;Learning with a team&lt;/h3&gt;&lt;p&gt;For other agencies, there are a few ways you can support your team as they learn together — you could open a Slack channel for people to ask &amp;amp; answer each other’s questions, watch videos together with a group discussion afterward, and work through coding challenges. Point-Free also has &lt;a href=&quot;https://www.pointfree.co/pricing&quot;&gt;special pricing&lt;/a&gt; for teams of two or more people, so you can get everyone on the same subscription plan.&lt;/p&gt;&lt;h3&gt;Wrapping up&lt;/h3&gt;&lt;p&gt;To summarize, we recommend following these steps to start your TCA journey:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Check out the &lt;a href=&quot;https://github.com/pointfreeco/swift-composable-architecture/blob/main/README.md&quot;&gt;README&lt;/a&gt; to understand the fundamentals &amp;amp; terminology&lt;/li&gt;&lt;li&gt;Follow the &lt;a href=&quot;https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/1.23.1/documentation/composablearchitecture/gettingstarted&quot;&gt;Getting Started&lt;/a&gt; guide&lt;/li&gt;&lt;li&gt;Do Chapter 1 of the &lt;a href=&quot;https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/1.23.1/tutorials/meetcomposablearchitecture/&quot;&gt;Point-Free tutorials&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Dip into the &lt;a href=&quot;https://www.pointfree.co/collections/composable-architecture/composable-architecture-1-0&quot;&gt;video series&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Do Chapter 2 of the Point-Free tutorials&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;All of these helpful resources will expose you to the core concepts of The Composable Architecture. Whether you’re someone who was already interested in checking out TCA or someone who hadn’t heard of it before, hopefully this gives you a little push into checking out something new!&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Learning is one of our core values at Lickability — that’s how we make sure we’re always delivering top-quality work to our clients. &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Reach out&lt;/a&gt; and let us know what we can help you with!&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Andrew Harrison</author></item><item><title>Our Apple Sports design critique</title><link>https://lickability.com/blog/apple-sports/</link><guid isPermaLink="true">https://lickability.com/blog/apple-sports/</guid><description>Breaking down the design details (so you don’t have to)</description><pubDate>Wed, 27 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few weeks ago, Apple did a surprise announcement of &lt;a href=&quot;https://apps.apple.com/us/app/apple-sports/id6446788829&quot;&gt;Apple Sports&lt;/a&gt;, a new app for sports fans. We do internal design critiques quite often, but we noticed a few things worth mentioning for designers and developers thinking about modern iOS design practices.&lt;/p&gt;&lt;p&gt;This app remixes stock UI components in really interesting ways. We once heard someone say, “design your app for the current iOS version + 1.” So with that lens, we’ve been looking at what the design of Apple Sports can tell us about iOS 18.&lt;/p&gt;&lt;p&gt;Apple has been playing with navigation bars a lot recently. We could probably write a whole post about just the nav bar. Take a look at how it stretches during over-scroll and transitions to a squished state when collapsed:&lt;/p&gt;&lt;div class=&quot;not-prose&quot;&gt; &lt;style&gt;astro-island,astro-slot,astro-static-slot{display:contents}&lt;/style&gt;&lt;script&gt;(()=&gt;{var l=(s,i,o)=&gt;{let r=async()=&gt;{await(await s())()},t=typeof i.value==&quot;object&quot;?i.value:void 0,c={rootMargin:t==null?void 0:t.rootMargin},n=new IntersectionObserver(e=&gt;{for(let a of e)if(a.isIntersecting){n.disconnect(),r();break}},c);for(let e of o.children)n.observe(e)};(self.Astro||(self.Astro={})).visible=l;window.dispatchEvent(new Event(&quot;astro:visible&quot;));})();;(()=&gt;{var A=Object.defineProperty;var g=(i,o,a)=&gt;o in i?A(i,o,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[o]=a;var d=(i,o,a)=&gt;g(i,typeof o!=&quot;symbol&quot;?o+&quot;&quot;:o,a);{let i={0:t=&gt;m(t),1:t=&gt;a(t),2:t=&gt;new RegExp(t),3:t=&gt;new Date(t),4:t=&gt;new Map(a(t)),5:t=&gt;new Set(a(t)),6:t=&gt;BigInt(t),7:t=&gt;new URL(t),8:t=&gt;new Uint8Array(t),9:t=&gt;new Uint16Array(t),10:t=&gt;new Uint32Array(t),11:t=&gt;1/0*t},o=t=&gt;{let[l,e]=t;return l in i?i[l](e):void 0},a=t=&gt;t.map(o),m=t=&gt;typeof t!=&quot;object&quot;||t===null?t:Object.fromEntries(Object.entries(t).map(([l,e])=&gt;[l,o(e)]));class y extends HTMLElement{constructor(){super(...arguments);d(this,&quot;Component&quot;);d(this,&quot;hydrator&quot;);d(this,&quot;hydrate&quot;,async()=&gt;{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest(&quot;astro-island[ssr]&quot;);if(e){e.addEventListener(&quot;astro:hydrate&quot;,this.hydrate,{once:!0});return}let c=this.querySelectorAll(&quot;astro-slot&quot;),n={},h=this.querySelectorAll(&quot;template[data-astro-template]&quot;);for(let r of h){let s=r.closest(this.tagName);s!=null&amp;&amp;s.isSameNode(this)&amp;&amp;(n[r.getAttribute(&quot;data-astro-template&quot;)||&quot;default&quot;]=r.innerHTML,r.remove())}for(let r of c){let s=r.closest(this.tagName);s!=null&amp;&amp;s.isSameNode(this)&amp;&amp;(n[r.getAttribute(&quot;name&quot;)||&quot;default&quot;]=r.innerHTML)}let p;try{p=this.hasAttribute(&quot;props&quot;)?m(JSON.parse(this.getAttribute(&quot;props&quot;))):{}}catch(r){let s=this.getAttribute(&quot;component-url&quot;)||&quot;&lt;unknown&gt;&quot;,v=this.getAttribute(&quot;component-export&quot;);throw v&amp;&amp;(s+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${s}`,this.getAttribute(&quot;props&quot;),r),r}let u;await this.hydrator(this)(this.Component,p,n,{client:this.getAttribute(&quot;client&quot;)}),this.removeAttribute(&quot;ssr&quot;),this.dispatchEvent(new CustomEvent(&quot;astro:hydrate&quot;))});d(this,&quot;unmount&quot;,()=&gt;{this.isConnected||this.dispatchEvent(new CustomEvent(&quot;astro:unmount&quot;))})}disconnectedCallback(){document.removeEventListener(&quot;astro:after-swap&quot;,this.unmount),document.addEventListener(&quot;astro:after-swap&quot;,this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute(&quot;await-children&quot;)||document.readyState===&quot;interactive&quot;||document.readyState===&quot;complete&quot;)this.childrenConnectedCallback();else{let e=()=&gt;{document.removeEventListener(&quot;DOMContentLoaded&quot;,e),c.disconnect(),this.childrenConnectedCallback()},c=new MutationObserver(()=&gt;{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&amp;&amp;this.lastChild.nodeValue===&quot;astro:end&quot;&amp;&amp;(this.lastChild.remove(),e())});c.observe(this,{childList:!0}),document.addEventListener(&quot;DOMContentLoaded&quot;,e)}}async childrenConnectedCallback(){let e=this.getAttribute(&quot;before-hydration-url&quot;);e&amp;&amp;await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute(&quot;opts&quot;)),c=this.getAttribute(&quot;client&quot;);if(Astro[c]===void 0){window.addEventListener(`astro:${c}`,()=&gt;this.start(),{once:!0});return}try{await Astro[c](async()=&gt;{let n=this.getAttribute(&quot;renderer-url&quot;),[h,{default:p}]=await Promise.all([import(this.getAttribute(&quot;component-url&quot;)),n?import(n):()=&gt;()=&gt;{}]),u=this.getAttribute(&quot;component-export&quot;)||&quot;default&quot;;if(!u.includes(&quot;.&quot;))this.Component=h[u];else{this.Component=h;for(let f of u.split(&quot;.&quot;))this.Component=this.Component[f]}return this.hydrator=p,this.hydrate},e,this)}catch(n){console.error(`[astro-island] Error hydrating ${this.getAttribute(&quot;component-url&quot;)}`,n)}}attributeChangedCallback(){this.hydrate()}}d(y,&quot;observedAttributes&quot;,[&quot;props&quot;]),customElements.get(&quot;astro-island&quot;)||customElements.define(&quot;astro-island&quot;,y)}})();&lt;/script&gt;&lt;astro-island uid=&quot;1ajtLD&quot; prefix=&quot;r0&quot; component-url=&quot;/opt/build/repo/embeds/ClientEmbed.tsx&quot; component-export=&quot;ClientEmbed&quot; renderer-url=&quot;@astrojs/react/client.js&quot; props=&quot;{&amp;quot;providerId&amp;quot;:[0,&amp;quot;vimeo&amp;quot;],&amp;quot;wrapperClass&amp;quot;:[0,&amp;quot;aspect-video&amp;quot;],&amp;quot;vimeoId&amp;quot;:[0,&amp;quot;928410041&amp;quot;]}&quot; ssr=&quot;&quot; client=&quot;visible&quot; before-hydration-url=&quot;astro:scripts/before-hydration.js&quot; opts=&quot;{&amp;quot;name&amp;quot;:&amp;quot;ClientEmbed&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;rootMargin&amp;quot;:&amp;quot;400px&amp;quot;}}&quot; await-children=&quot;&quot;&gt;&lt;div class=&quot;aspect-video&quot;&gt;&lt;div data-testid=&quot;general-observer&quot; class=&quot;mdx-embed&quot;&gt;&lt;div style=&quot;height:0;width:100%&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--astro:end--&gt;&lt;/astro-island&gt; &lt;/div&gt;&lt;p&gt;Gradients are used a LOT in this app. This nav bar gradient effect is becoming pretty common throughout first-party apps as well — first with the iOS 17 Health app, then Journal, and now Sports. watchOS 10 is also full of gradients.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;The home screen for Apple Sports is shown on the iPhone 15 Pro, prompting the user to set up their experience. A faint green fades from the top of the screen to black below the nav bar.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f31ca5191c3f2f927fd12ad4351623a6607c726f-2744x3840.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/f31ca5191c3f2f927fd12ad4351623a6607c726f-2744x3840.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/f31ca5191c3f2f927fd12ad4351623a6607c726f-2744x3840.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/f31ca5191c3f2f927fd12ad4351623a6607c726f-2744x3840.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/f31ca5191c3f2f927fd12ad4351623a6607c726f-2744x3840.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/f31ca5191c3f2f927fd12ad4351623a6607c726f-2744x3840.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/f31ca5191c3f2f927fd12ad4351623a6607c726f-2744x3840.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2744 2744w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f31ca5191c3f2f927fd12ad4351623a6607c726f-2744x3840.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;2239&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the Journal app, with a journal entry over a dark purple gradient background.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c1ddb1cec5ce84b8a32741c8cd9a9b3fc91630a3-1179x2556.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=295 295w, https://cdn.sanity.io/images/nkt6o869/production/c1ddb1cec5ce84b8a32741c8cd9a9b3fc91630a3-1179x2556.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=590 590w, https://cdn.sanity.io/images/nkt6o869/production/c1ddb1cec5ce84b8a32741c8cd9a9b3fc91630a3-1179x2556.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=884 884w, https://cdn.sanity.io/images/nkt6o869/production/c1ddb1cec5ce84b8a32741c8cd9a9b3fc91630a3-1179x2556.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1179 1179w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c1ddb1cec5ce84b8a32741c8cd9a9b3fc91630a3-1179x2556.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1179&quot; width=&quot;1179&quot; height=&quot;2556&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;The Journal app is entirely gradients!&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Take a look at the “Yesterday / Today / Upcoming” picker right above the list. This is functionally a segmented control, but they did not opt to use the stock style. Instead, they’re making heavy use of the system-provided &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/material&quot;&gt;vibrancy effect&lt;/a&gt;. The default control is blockier and stands out more, which wouldn’t make a ton of sense in this context where it isn’t a super important control.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;The My Teams page within Apple Sports, showing a list of upcoming games. At the top, the words Yesterday and Upcoming are dimmed, while the word Today is bright and selected. A bit of the green background bleeds through the labels.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/beec925096855b62aed174feb72bd5fa13f9ba27-2744x3840.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/beec925096855b62aed174feb72bd5fa13f9ba27-2744x3840.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/beec925096855b62aed174feb72bd5fa13f9ba27-2744x3840.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/beec925096855b62aed174feb72bd5fa13f9ba27-2744x3840.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/beec925096855b62aed174feb72bd5fa13f9ba27-2744x3840.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/beec925096855b62aed174feb72bd5fa13f9ba27-2744x3840.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/beec925096855b62aed174feb72bd5fa13f9ba27-2744x3840.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2744 2744w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/beec925096855b62aed174feb72bd5fa13f9ba27-2744x3840.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;2239&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;If you’ve used the new &lt;a href=&quot;https://www.figma.com/community/file/1248375255495415511&quot;&gt;iOS Design Library in Figma&lt;/a&gt; and noticed color styles like “Vibrant Secondary Dark,” you’ll see this effect emulated. This isn’t 1:1 with the actual iOS implementation, but it’s a good visualization.&lt;/p&gt;&lt;p&gt;In dark mode, a standard secondary label color is just off-white at 60% opacity. But the vibrant label styles are more complex — they let the background color shine through by stacking multiple fill colors with various blend modes.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Two text boxes in Figma with one selected, showing a vibrant secondary label. The inspector reveals two fill colors at different opacities, one with an overlay blend mode.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/12abb581f4feb9e4b78725950215a2e4735d410d-2490x1518.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/12abb581f4feb9e4b78725950215a2e4735d410d-2490x1518.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/12abb581f4feb9e4b78725950215a2e4735d410d-2490x1518.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/12abb581f4feb9e4b78725950215a2e4735d410d-2490x1518.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/12abb581f4feb9e4b78725950215a2e4735d410d-2490x1518.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/12abb581f4feb9e4b78725950215a2e4735d410d-2490x1518.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/12abb581f4feb9e4b78725950215a2e4735d410d-2490x1518.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2490 2490w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/12abb581f4feb9e4b78725950215a2e4735d410d-2490x1518.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;975&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;There’s a lot of fun typography, too! Take a look at the score numbers — these use the system variable font features to make the weight bold and the width compact. We love messing with the font width axes for display type to see what effects we can achieve.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Betting odds and team stats for a live game in Apple Sports. The game scores use a condensed, bold font.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/784272b172a4a4d8066203fa9b6fe2d4100c1b7d-1463x2048.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=366 366w, https://cdn.sanity.io/images/nkt6o869/production/784272b172a4a4d8066203fa9b6fe2d4100c1b7d-1463x2048.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=732 732w, https://cdn.sanity.io/images/nkt6o869/production/784272b172a4a4d8066203fa9b6fe2d4100c1b7d-1463x2048.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1097 1097w, https://cdn.sanity.io/images/nkt6o869/production/784272b172a4a4d8066203fa9b6fe2d4100c1b7d-1463x2048.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1463 1463w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/784272b172a4a4d8066203fa9b6fe2d4100c1b7d-1463x2048.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1463&quot; width=&quot;1463&quot; height=&quot;2048&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;This is also the first time we can recall a super dense data grid in a first-party app. These are always a huge pain to construct, but they’ve implemented this really well. Take a look at what happens when you increase the Dynamic Type size, too:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;The details for an upcoming game, with betting odds and division standings on a translucent platter. The headers use vibrancy, with a bit of the background color coming through.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/18d3ebf20a5bcdab291bc77d52a9583d69bbcfe7-945x2048.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=236 236w, https://cdn.sanity.io/images/nkt6o869/production/18d3ebf20a5bcdab291bc77d52a9583d69bbcfe7-945x2048.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=473 473w, https://cdn.sanity.io/images/nkt6o869/production/18d3ebf20a5bcdab291bc77d52a9583d69bbcfe7-945x2048.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=709 709w, https://cdn.sanity.io/images/nkt6o869/production/18d3ebf20a5bcdab291bc77d52a9583d69bbcfe7-945x2048.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=945 945w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/18d3ebf20a5bcdab291bc77d52a9583d69bbcfe7-945x2048.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=945&quot; width=&quot;945&quot; height=&quot;2048&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;The same table shown in the previous image, but the fonts are about 30 percent larger. Team names in the division standings are truncated, but the columns remain aligned. Table headers wrap onto additional lines instead of truncating.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/10b25c07487386dacd53c890396bb323dfbc15b6-945x2048.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=236 236w, https://cdn.sanity.io/images/nkt6o869/production/10b25c07487386dacd53c890396bb323dfbc15b6-945x2048.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=473 473w, https://cdn.sanity.io/images/nkt6o869/production/10b25c07487386dacd53c890396bb323dfbc15b6-945x2048.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=709 709w, https://cdn.sanity.io/images/nkt6o869/production/10b25c07487386dacd53c890396bb323dfbc15b6-945x2048.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=945 945w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/10b25c07487386dacd53c890396bb323dfbc15b6-945x2048.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=945&quot; width=&quot;945&quot; height=&quot;2048&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Finally (and this is a big one): motion backgrounds. It’s easy for an app to feel lifeless when it’s just displaying static data, like the Weather and Sports apps. It’s really hard to get right, but they’ve implemented this animation quite well.&lt;/p&gt;&lt;div class=&quot;not-prose&quot;&gt; &lt;astro-island uid=&quot;2ddPmK&quot; prefix=&quot;r8&quot; component-url=&quot;/opt/build/repo/embeds/ClientEmbed.tsx&quot; component-export=&quot;ClientEmbed&quot; renderer-url=&quot;@astrojs/react/client.js&quot; props=&quot;{&amp;quot;providerId&amp;quot;:[0,&amp;quot;vimeo&amp;quot;],&amp;quot;wrapperClass&amp;quot;:[0,&amp;quot;aspect-video&amp;quot;],&amp;quot;vimeoId&amp;quot;:[0,&amp;quot;928411015&amp;quot;]}&quot; ssr=&quot;&quot; client=&quot;visible&quot; before-hydration-url=&quot;astro:scripts/before-hydration.js&quot; opts=&quot;{&amp;quot;name&amp;quot;:&amp;quot;ClientEmbed&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;rootMargin&amp;quot;:&amp;quot;400px&amp;quot;}}&quot; await-children=&quot;&quot;&gt;&lt;div class=&quot;aspect-video&quot;&gt;&lt;div data-testid=&quot;general-observer&quot; class=&quot;mdx-embed&quot;&gt;&lt;div style=&quot;height:0;width:100%&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--astro:end--&gt;&lt;/astro-island&gt; &lt;/div&gt;&lt;p&gt;It’s hard to tell what’s going on, but here’s our guess: There are two color swatches where the exact gradation point shifts a little bit (maybe ± 10% from the middle). Highlights shimmer on top of that gradient. Then, underneath it all, this cloth texture we extracted from the app bundle is tiled and warped by a Metal shader.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A jersey cloth repeating texture&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/2095435ca431f747ad7fa133d10d5580ce02dd90-239x239.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=120 120w, https://cdn.sanity.io/images/nkt6o869/production/2095435ca431f747ad7fa133d10d5580ce02dd90-239x239.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=239 239w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/2095435ca431f747ad7fa133d10d5580ce02dd90-239x239.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=239&quot; width=&quot;239&quot; height=&quot;239&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Will iOS 18 include more apps with this jersey cloth texture? Almost certainly not. But to make your app feel ahead of the curve, you gotta (and excuse the cheesiness) skate to where the puck is going to be. And the puck is headed towards a lot of gradients, motion, and remixing the nav bar in fun ways.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;This post first appeared as a thread on &lt;a href=&quot;https://twitter.com/lickability/status/1762199274475991412&quot;&gt;X&lt;/a&gt; and &lt;a href=&quot;https://mastodon.social/@lickability/111999803783162098&quot;&gt;Mastodon&lt;/a&gt;. Go follow us there for more design content like this!&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Sam Henri Gold</author></item><item><title>Monetizing your app with Superwall</title><link>https://lickability.com/blog/monetizing-your-app-with-superwall/</link><guid isPermaLink="true">https://lickability.com/blog/monetizing-your-app-with-superwall/</guid><description>We can be your paywall pals</description><pubDate>Tue, 27 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You may already know us as a &lt;a href=&quot;https://lickability.com/blog/revenuecat/&quot;&gt;RevenueCat partner&lt;/a&gt; — now, we’re adding a new partnership with &lt;a href=&quot;https://superwall.com/&quot;&gt;Superwall&lt;/a&gt; to the mix so we can do even more to help our clients monetize their apps.&lt;/p&gt;&lt;h3&gt;👋 Meet Superwall&lt;/h3&gt;&lt;p&gt;It is a truth universally acknowledged that if you want to monetize your app, you’re going to need a &lt;a href=&quot;https://www.paywallscreens.com/&quot;&gt;paywall&lt;/a&gt;. That’s where Superwall comes in: using one of their &lt;a href=&quot;https://templates.superwall.com/release/latest/gallery/&quot;&gt;many templates&lt;/a&gt;, you can build and deploy beautiful paywalls easier than ever before. Once the Superwall SDK is set up in your app, all it takes is a few clicks on their dashboard to customize your paywalls with their drag and drop editor — no app updates required.&lt;/p&gt;&lt;p&gt;As an added bonus, they’ll use their expertise to help you craft your first unique paywall for free:&lt;/p&gt;&lt;blockquote&gt;Superwall has built hundreds of paywalls for clients and has a unique vantage point on the industry. As a token of gratitude for signing up, we create a paywall for each customer, using best practices we’ve picked up along the way.&lt;/blockquote&gt;&lt;p&gt;&lt;em&gt;— via the &lt;a href=&quot;https://superwall.com/docs/home&quot;&gt;Superwall documentation&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;🐱 What about RevenueCat?&lt;/h3&gt;&lt;p&gt;Good news: Superwall &amp;amp; &lt;a href=&quot;https://www.revenuecat.com/&quot;&gt;RevenueCat&lt;/a&gt; play well together, so you can use them both to manage your in-app subscriptions with no problems. &lt;em&gt;(Psst, did we mention we’re also a certified &lt;a href=&quot;https://www.revenuecat.com/partners/agency/lickability&quot;&gt;Premier Partner&lt;/a&gt; of RevenueCat?)&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;🏁 Getting started&lt;/h3&gt;&lt;p&gt;If you’re an indie developer looking to add a paywall to your iOS app, we highly recommend checking out Superwall’s &lt;a href=&quot;https://superwall.com/blog/getting-started-with-superwall-in-your-indie-ios-app&quot;&gt;three-step guide&lt;/a&gt; to getting up and running. For design inspiration, it’s definitely worth browsing the hundreds of examples they’ve collected in &lt;a href=&quot;https://www.paywallscreens.com/&quot;&gt;this paywall gallery&lt;/a&gt;.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;If you need a little more help setting up the perfect paywall using Superwall’s best practices, we’re &lt;a href=&quot;https://lickability.com/contact&quot;&gt;just a click away&lt;/a&gt;! As Superwall partners, we’d love to work with you to make it as easy as possible to monetize your app.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Team Lickability</author></item><item><title>15 Years</title><link>https://lickability.com/blog/15-years/</link><guid isPermaLink="true">https://lickability.com/blog/15-years/</guid><description>Looking back on the past five years</description><pubDate>Mon, 12 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It’s wild to think about, but Lickability has now been in business for fifteen years. What started as two high school students building apps together over iChat AV has grown to a team of ten talented engineers, designers, and operations folks creating beautiful software used by millions of people.&lt;/p&gt;&lt;p&gt;I covered &lt;a href=&quot;/blog/10-years-of-lickability&quot; title=&quot;10 Years of Lickability&quot; data-astro-cid-okzjwv6e&gt;the first ten years&lt;/a&gt;  of our history a previous blog post, which focused on our start as a small product team and growth into a small studio. The last five years of our story have been shaped by a global pandemic, and our resilience and ingenuity as the industry has matured.&lt;/p&gt;&lt;h3&gt;2020&lt;/h3&gt;&lt;div class=&quot;not-prose&quot;&gt; &lt;style&gt;astro-island,astro-slot,astro-static-slot{display:contents}&lt;/style&gt;&lt;script&gt;(()=&gt;{var l=(s,i,o)=&gt;{let r=async()=&gt;{await(await s())()},t=typeof i.value==&quot;object&quot;?i.value:void 0,c={rootMargin:t==null?void 0:t.rootMargin},n=new IntersectionObserver(e=&gt;{for(let a of e)if(a.isIntersecting){n.disconnect(),r();break}},c);for(let e of o.children)n.observe(e)};(self.Astro||(self.Astro={})).visible=l;window.dispatchEvent(new Event(&quot;astro:visible&quot;));})();;(()=&gt;{var A=Object.defineProperty;var g=(i,o,a)=&gt;o in i?A(i,o,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[o]=a;var d=(i,o,a)=&gt;g(i,typeof o!=&quot;symbol&quot;?o+&quot;&quot;:o,a);{let i={0:t=&gt;m(t),1:t=&gt;a(t),2:t=&gt;new RegExp(t),3:t=&gt;new Date(t),4:t=&gt;new Map(a(t)),5:t=&gt;new Set(a(t)),6:t=&gt;BigInt(t),7:t=&gt;new URL(t),8:t=&gt;new Uint8Array(t),9:t=&gt;new Uint16Array(t),10:t=&gt;new Uint32Array(t),11:t=&gt;1/0*t},o=t=&gt;{let[l,e]=t;return l in i?i[l](e):void 0},a=t=&gt;t.map(o),m=t=&gt;typeof t!=&quot;object&quot;||t===null?t:Object.fromEntries(Object.entries(t).map(([l,e])=&gt;[l,o(e)]));class y extends HTMLElement{constructor(){super(...arguments);d(this,&quot;Component&quot;);d(this,&quot;hydrator&quot;);d(this,&quot;hydrate&quot;,async()=&gt;{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest(&quot;astro-island[ssr]&quot;);if(e){e.addEventListener(&quot;astro:hydrate&quot;,this.hydrate,{once:!0});return}let c=this.querySelectorAll(&quot;astro-slot&quot;),n={},h=this.querySelectorAll(&quot;template[data-astro-template]&quot;);for(let r of h){let s=r.closest(this.tagName);s!=null&amp;&amp;s.isSameNode(this)&amp;&amp;(n[r.getAttribute(&quot;data-astro-template&quot;)||&quot;default&quot;]=r.innerHTML,r.remove())}for(let r of c){let s=r.closest(this.tagName);s!=null&amp;&amp;s.isSameNode(this)&amp;&amp;(n[r.getAttribute(&quot;name&quot;)||&quot;default&quot;]=r.innerHTML)}let p;try{p=this.hasAttribute(&quot;props&quot;)?m(JSON.parse(this.getAttribute(&quot;props&quot;))):{}}catch(r){let s=this.getAttribute(&quot;component-url&quot;)||&quot;&lt;unknown&gt;&quot;,v=this.getAttribute(&quot;component-export&quot;);throw v&amp;&amp;(s+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${s}`,this.getAttribute(&quot;props&quot;),r),r}let u;await this.hydrator(this)(this.Component,p,n,{client:this.getAttribute(&quot;client&quot;)}),this.removeAttribute(&quot;ssr&quot;),this.dispatchEvent(new CustomEvent(&quot;astro:hydrate&quot;))});d(this,&quot;unmount&quot;,()=&gt;{this.isConnected||this.dispatchEvent(new CustomEvent(&quot;astro:unmount&quot;))})}disconnectedCallback(){document.removeEventListener(&quot;astro:after-swap&quot;,this.unmount),document.addEventListener(&quot;astro:after-swap&quot;,this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute(&quot;await-children&quot;)||document.readyState===&quot;interactive&quot;||document.readyState===&quot;complete&quot;)this.childrenConnectedCallback();else{let e=()=&gt;{document.removeEventListener(&quot;DOMContentLoaded&quot;,e),c.disconnect(),this.childrenConnectedCallback()},c=new MutationObserver(()=&gt;{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&amp;&amp;this.lastChild.nodeValue===&quot;astro:end&quot;&amp;&amp;(this.lastChild.remove(),e())});c.observe(this,{childList:!0}),document.addEventListener(&quot;DOMContentLoaded&quot;,e)}}async childrenConnectedCallback(){let e=this.getAttribute(&quot;before-hydration-url&quot;);e&amp;&amp;await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute(&quot;opts&quot;)),c=this.getAttribute(&quot;client&quot;);if(Astro[c]===void 0){window.addEventListener(`astro:${c}`,()=&gt;this.start(),{once:!0});return}try{await Astro[c](async()=&gt;{let n=this.getAttribute(&quot;renderer-url&quot;),[h,{default:p}]=await Promise.all([import(this.getAttribute(&quot;component-url&quot;)),n?import(n):()=&gt;()=&gt;{}]),u=this.getAttribute(&quot;component-export&quot;)||&quot;default&quot;;if(!u.includes(&quot;.&quot;))this.Component=h[u];else{this.Component=h;for(let f of u.split(&quot;.&quot;))this.Component=this.Component[f]}return this.hydrator=p,this.hydrate},e,this)}catch(n){console.error(`[astro-island] Error hydrating ${this.getAttribute(&quot;component-url&quot;)}`,n)}}attributeChangedCallback(){this.hydrate()}}d(y,&quot;observedAttributes&quot;,[&quot;props&quot;]),customElements.get(&quot;astro-island&quot;)||customElements.define(&quot;astro-island&quot;,y)}})();&lt;/script&gt;&lt;astro-island uid=&quot;Z25u3JS&quot; prefix=&quot;r0&quot; component-url=&quot;/opt/build/repo/embeds/ClientEmbed.tsx&quot; component-export=&quot;ClientEmbed&quot; renderer-url=&quot;@astrojs/react/client.js&quot; props=&quot;{&amp;quot;providerId&amp;quot;:[0,&amp;quot;twitter&amp;quot;],&amp;quot;wrapperClass&amp;quot;:[0,&amp;quot;aspect-video&amp;quot;],&amp;quot;tweetLink&amp;quot;:[0,&amp;quot;lickability/status/1310976065012105228&amp;quot;]}&quot; ssr=&quot;&quot; client=&quot;visible&quot; before-hydration-url=&quot;astro:scripts/before-hydration.js&quot; opts=&quot;{&amp;quot;name&amp;quot;:&amp;quot;ClientEmbed&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;rootMargin&amp;quot;:&amp;quot;400px&amp;quot;}}&quot; await-children=&quot;&quot;&gt;&lt;div class=&quot;aspect-video&quot;&gt;&lt;div data-testid=&quot;general-observer&quot; class=&quot;mdx-embed&quot;&gt;&lt;div style=&quot;height:0;width:100%&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--astro:end--&gt;&lt;/astro-island&gt; &lt;/div&gt;&lt;p&gt;2020 was the year we learned how to be productive as a fully remote team. In response to the pandemic, we closed our office and started using Zoom, held virtual happy hours, and released &lt;a href=&quot;/blog/meet-buildwatch&quot; title=&quot;Meet Buildwatch&quot; data-astro-cid-okzjwv6e&gt;Buildwatch&lt;/a&gt;  to help developers track and reclaim their time spent compiling.&lt;/p&gt;&lt;p&gt;We hired our first full-time designer, Sam Gold. And we worked with incredible clients like Clubhouse, Pair Play, Mixtiles, and Fountain to ship new mobile experiences that were informed by the pandemic and helped people find friends, dates, and jobs online as the world remained locked down.&lt;/p&gt;&lt;h3&gt;2021&lt;/h3&gt;&lt;div class=&quot;not-prose&quot;&gt; &lt;astro-island uid=&quot;2m0GOl&quot; prefix=&quot;r1&quot; component-url=&quot;/opt/build/repo/embeds/ClientEmbed.tsx&quot; component-export=&quot;ClientEmbed&quot; renderer-url=&quot;@astrojs/react/client.js&quot; props=&quot;{&amp;quot;providerId&amp;quot;:[0,&amp;quot;twitter&amp;quot;],&amp;quot;wrapperClass&amp;quot;:[0,&amp;quot;aspect-video&amp;quot;],&amp;quot;tweetLink&amp;quot;:[0,&amp;quot;lickability/status/1469063740431679489&amp;quot;]}&quot; ssr=&quot;&quot; client=&quot;visible&quot; before-hydration-url=&quot;astro:scripts/before-hydration.js&quot; opts=&quot;{&amp;quot;name&amp;quot;:&amp;quot;ClientEmbed&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;rootMargin&amp;quot;:&amp;quot;400px&amp;quot;}}&quot; await-children=&quot;&quot;&gt;&lt;div class=&quot;aspect-video&quot;&gt;&lt;div data-testid=&quot;general-observer&quot; class=&quot;mdx-embed&quot;&gt;&lt;div style=&quot;height:0;width:100%&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--astro:end--&gt;&lt;/astro-island&gt; &lt;/div&gt;&lt;p&gt;As we &lt;a href=&quot;/blog/closing-our-office&quot; title=&quot;Closing Our Office&quot; data-astro-cid-okzjwv6e&gt;closed our office&lt;/a&gt;  and transitioned to being fully remote, we experimented with more ways to engage with the wider developer community with our live show &lt;a href=&quot;https://lickability.com/blog/welcome-to-club-mobile/&quot;&gt;Club Mobile&lt;/a&gt;, &lt;a href=&quot;https://x.com/lickability/status/1294295481812750336?s=20&quot;&gt;#iOSDevTips thread&lt;/a&gt;, &lt;a href=&quot;https://x.com/lickability/status/1448708755839983616?s=20&quot;&gt;talks at universities&lt;/a&gt;, and &lt;a href=&quot;https://x.com/lickability/status/1401905226169438209?s=20&quot;&gt;WWDC Discord&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;We continued our long-running partnership with &lt;a href=&quot;/blog/the-atlantic-app-a-mobile-revival&quot; title=&quot;The Atlantic App—A Mobile Revival&quot; data-astro-cid-okzjwv6e&gt;The Atlantic&lt;/a&gt;  and took on new projects with clients like &lt;a href=&quot;/blog/designing-mastodons-reply-safety-features&quot; title=&quot;Designing Mastodon’s reply safety features&quot; data-astro-cid-okzjwv6e&gt;Mastodon&lt;/a&gt;  and &lt;a href=&quot;https://www.flavrs.com/&quot;&gt;Flavrs&lt;/a&gt;, where we got to own both design and development. In May, we shipped another new app of our own, &lt;a href=&quot;/blog/scorecard-is-here&quot; title=&quot;Scorecard is Here&quot; data-astro-cid-okzjwv6e&gt;Scorecard&lt;/a&gt; , for tracking scores in tabletop games.&lt;/p&gt;&lt;h3&gt;2022&lt;/h3&gt;&lt;div class=&quot;not-prose&quot;&gt; &lt;astro-island uid=&quot;TE3Au&quot; prefix=&quot;r2&quot; component-url=&quot;/opt/build/repo/embeds/ClientEmbed.tsx&quot; component-export=&quot;ClientEmbed&quot; renderer-url=&quot;@astrojs/react/client.js&quot; props=&quot;{&amp;quot;providerId&amp;quot;:[0,&amp;quot;twitter&amp;quot;],&amp;quot;wrapperClass&amp;quot;:[0,&amp;quot;aspect-video&amp;quot;],&amp;quot;tweetLink&amp;quot;:[0,&amp;quot;lickability/status/1554149962941751296&amp;quot;]}&quot; ssr=&quot;&quot; client=&quot;visible&quot; before-hydration-url=&quot;astro:scripts/before-hydration.js&quot; opts=&quot;{&amp;quot;name&amp;quot;:&amp;quot;ClientEmbed&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;rootMargin&amp;quot;:&amp;quot;400px&amp;quot;}}&quot; await-children=&quot;&quot;&gt;&lt;div class=&quot;aspect-video&quot;&gt;&lt;div data-testid=&quot;general-observer&quot; class=&quot;mdx-embed&quot;&gt;&lt;div style=&quot;height:0;width:100%&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--astro:end--&gt;&lt;/astro-island&gt; &lt;/div&gt;&lt;p&gt;Our first &lt;a href=&quot;/blog/our-first-retreat&quot; title=&quot;Our first retreat&quot; data-astro-cid-okzjwv6e&gt;company retreat in Brooklyn&lt;/a&gt;  brought the team together in person, and gave us space to reconnect and plan for the next fifteen years of Lickability. Our engineers &lt;a href=&quot;https://x.com/lickability/status/1545109812484120579?s=20&quot;&gt;spoke at conferences&lt;/a&gt; around the world as the community began gathering IRL again, we &lt;a href=&quot;/blog/redesigning-our-contracts&quot; title=&quot;Redesigning our documents&quot; data-astro-cid-okzjwv6e&gt;redesigned our key documents&lt;/a&gt; , and &lt;a href=&quot;https://x.com/lickability/status/1529494191344328705?s=20&quot;&gt;dropped a playlist&lt;/a&gt; containing years of music recommendations that power our teams.&lt;/p&gt;&lt;p&gt;Ken joined the engineering team full-time, and our client roster grew to include &lt;a href=&quot;https://www.joinloopgolf.co/&quot;&gt;Loop Golf&lt;/a&gt;, &lt;a href=&quot;https://www.musicsketch.com/&quot;&gt;MusicSketch&lt;/a&gt;, and &lt;a href=&quot;https://directaffect.net/&quot;&gt;Direct Affect&lt;/a&gt;, broadening our experience in the sports, music, and philanthropy industries.&lt;/p&gt;&lt;h3&gt;2023&lt;/h3&gt;&lt;div class=&quot;not-prose&quot;&gt; &lt;astro-island uid=&quot;Z1m51Ez&quot; prefix=&quot;r3&quot; component-url=&quot;/opt/build/repo/embeds/ClientEmbed.tsx&quot; component-export=&quot;ClientEmbed&quot; renderer-url=&quot;@astrojs/react/client.js&quot; props=&quot;{&amp;quot;providerId&amp;quot;:[0,&amp;quot;twitter&amp;quot;],&amp;quot;wrapperClass&amp;quot;:[0,&amp;quot;aspect-video&amp;quot;],&amp;quot;tweetLink&amp;quot;:[0,&amp;quot;lickability/status/1730325080972898389&amp;quot;]}&quot; ssr=&quot;&quot; client=&quot;visible&quot; before-hydration-url=&quot;astro:scripts/before-hydration.js&quot; opts=&quot;{&amp;quot;name&amp;quot;:&amp;quot;ClientEmbed&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;rootMargin&amp;quot;:&amp;quot;400px&amp;quot;}}&quot; await-children=&quot;&quot;&gt;&lt;div class=&quot;aspect-video&quot;&gt;&lt;div data-testid=&quot;general-observer&quot; class=&quot;mdx-embed&quot;&gt;&lt;div style=&quot;height:0;width:100%&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--astro:end--&gt;&lt;/astro-island&gt; &lt;/div&gt;&lt;p&gt;Taking the energy from &lt;a href=&quot;https://config.figma.com/&quot;&gt;Config&lt;/a&gt; and bringing it to New York, 2023 saw the launch of our Design Friends meetup, where we invite designers and developers to hang out and talk shop once a month at one of our favorite local establishments. We also hosted &lt;a href=&quot;https://x.com/lickability/status/1719761785564098789?s=20&quot;&gt;our first webinar&lt;/a&gt; on choosing the right technology for your mobile app, launched the &lt;a href=&quot;https://lickability.us2.list-manage.com/subscribe?u=8a254564acdd2915e0dd0d719&amp;id=c71805921b&quot;&gt;Soft Serve newsletter&lt;/a&gt; to share insights and links from around the web, and &lt;a href=&quot;/blog/detecting-collisions-with-realitykit-in-visionos&quot; title=&quot;Detecting Collisions with RealityKit in visionOS&quot; data-astro-cid-okzjwv6e&gt;prototyped an app idea within visionOS&lt;/a&gt; .&lt;/p&gt;&lt;p&gt;We shipped &lt;a href=&quot;https://appaudit.net&quot;&gt;App Audit&lt;/a&gt; as a way for us to get started working with teams more quickly, and we became both a &lt;a href=&quot;/blog/revenuecat&quot; title=&quot;We’re a RevenueCat partner!&quot; data-astro-cid-okzjwv6e&gt;RevenueCat&lt;/a&gt;  partner and &lt;a href=&quot;/blog/working-with-clutch&quot; title=&quot;Working with Clutch&quot; data-astro-cid-okzjwv6e&gt;recognized by Clutch&lt;/a&gt;  as one of the best app development studios in the country.&lt;/p&gt;&lt;h3&gt;Onward&lt;/h3&gt;&lt;p&gt;Lickability continues to be my favorite place I’ve ever worked, and the only way to keep it that way is to keep improving the way we work together — just as we have over the last decade and a half. To keep treating our people and customers with respect, building software that feels as good as it looks, and having fun doing it.&lt;/p&gt;&lt;p&gt;Thanks for all of your support over the years in building this studio. If you’d like to work together on building better software, I know &lt;a href=&quot;https://lickability.com/contact&quot;&gt;just the team&lt;/a&gt; who’d love to help.&lt;/p&gt; </content:encoded><author>mb bischoff</author></item><item><title>Mastering App Intents: Querying Made Easy</title><link>https://lickability.com/blog/app-intents/</link><guid isPermaLink="true">https://lickability.com/blog/app-intents/</guid><description>🔍 Uncover the secrets of effective data retrieval in iOS development</description><pubDate>Thu, 01 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;App Intents are one of the most powerful features on iOS, but there is no clear pattern for sharing data and logic between your app and your App Intent. In this post, we will be building a simple to-do list to show how to share data between your app‘s existing data model and an App Intent. We will be focusing on a single item: marking a task in the list as complete. The GitHub repository of this project can be found &lt;a href=&quot;https://github.com/ashlirankin18/Simply&quot;&gt;here&lt;/a&gt; if you would like to code along!&lt;/p&gt;&lt;h3&gt;Getting started&lt;/h3&gt;&lt;p&gt;Let’s start by creating a new file in the Xcode project called &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Complete​Task​Intent&lt;/code&gt;. In this file, create a &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;struct&lt;/code&gt; called &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Complete​Task​Intent&lt;/code&gt;, and conform it to the &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;App​Intents&lt;/code&gt; protocol. For a more in-depth look into the components for setting up App Intents, you can visit my &lt;a href=&quot;https://lickability.com/blog/creating-your-first-app-shortcut/&quot;&gt;previous post&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; To conform to the &lt;code index=&quot;2&quot; isInline=&quot;true&quot;&gt;App​Intents&lt;/code&gt; protocol, you need to add a title and the perform method.&lt;/p&gt;&lt;p&gt;Your file should look similar to this:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; CompleteTaskIntent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AppIntent &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; title: LocalizedStringResource = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Complete Task&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; perform&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; IntentResult {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;If you run the above logic in Xcode, open the Shortcuts app, and tap the &lt;strong&gt;+&lt;/strong&gt; button to add a new shortcut, you should see this intent displayed under the “Apps” section of actions to add to your shortcut.&lt;/p&gt;&lt;h3&gt;Adding parameters&lt;/h3&gt;&lt;p&gt;Now, let’s add our first property: the date.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; CompleteTaskIntent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AppIntent &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  @Parameter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(title: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Date&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; date: Date&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; title: LocalizedStringResource = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Complete Task&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; perform&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; IntentResult {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;You may notice that &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;date&lt;/code&gt; is a non-optional parameter. In this context, we are indicating to the system that this is a required parameter.&lt;/p&gt;&lt;p&gt;Now, lets add our second parameter: the task. Create a new parameter for your task like we did before — this time, the type of parameter is &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Todo​Task&lt;/code&gt;.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; CompleteTaskIntent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AppIntent &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  @Parameter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(title: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Date&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; date: Date&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  @Parameter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(title: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Task&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; task: TodoTask&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; title: LocalizedStringResource = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Complete Task&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; perform&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; IntentResult {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;h3&gt;This code doesn‘t compile!&lt;/h3&gt;&lt;p&gt;The system is not aware of our app’s data model, so this code does not compile. To expose this “custom data type” to the system, we must put it in a form the system can recognize. In the App Intents framework, the way we do this is to create a new model conforming to the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;App​Entity&lt;/code&gt;.&lt;/p&gt;&lt;blockquote&gt;An interface for exposing a custom type or app-specific concept to system services such as Siri and the Shortcuts app.&lt;/blockquote&gt;&lt;p&gt;– &lt;a href=&quot;https://developer.apple.com/documentation/appintents/appentity&quot;&gt;Apple&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Once we’ve conformed our model to this protocol, the code will compile.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/documentation/appintents/appentity&quot;&gt;Apple recommends&lt;/a&gt; creating another structure representing the parts of your data model that your intent will supply the information to.&lt;/p&gt;&lt;p&gt;For this exercise, we will extend our data model to conform to the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;App​Entity&lt;/code&gt; protocol. You can add this code to a new file, or to the file containing the data model.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;extension&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; TodoTask&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AppEntity &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; TaskQuery&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;EntityQuery &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; entities&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;Self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.Entity.ID]) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; [&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;Self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.Entity] {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;          return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; suggestedEntities&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;Self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.Result {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;          return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; displayRepresentation: DisplayRepresentation {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;stringLiteral&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; defaultQuery: TaskQuery = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;TaskQuery&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; typeDisplayRepresentation: TypeDisplayRepresentation = .&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Task&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;To conform to the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;App​Entity&lt;/code&gt; protocol, we need to implement three properties:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;display​Representation: Display​Representation&lt;/code&gt;: This property defines how the custom type will be displayed in the UI. You can specify the &lt;code index=&quot;2&quot; isInline=&quot;true&quot;&gt;title&lt;/code&gt;, &lt;code index=&quot;4&quot; isInline=&quot;true&quot;&gt;subtitle&lt;/code&gt;, &lt;code index=&quot;6&quot; isInline=&quot;true&quot;&gt;synomyms&lt;/code&gt; and &lt;code index=&quot;8&quot; isInline=&quot;true&quot;&gt;image&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;default​Query: Entity​Query&lt;/code&gt;: This provides the default query to be used when querying and retrieving entities with Shortcuts and Siri. When trying to resolve a parameter, the system will perform these queries to provide entities to be used by the UI.&lt;/li&gt;&lt;li&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;type​Display​Representation: Type​Display​Representation&lt;/code&gt;: A short, localized, human-readable name for the type.&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Creating a query&lt;/h3&gt;&lt;p&gt;Let’s take a closer look at query creation. &lt;a href=&quot;https://developer.apple.com/documentation/appintents/entity-queries&quot;&gt;According to Apple‘s documentation&lt;/a&gt;, when the system needs to retrieve one or more specific instances of an app entity, it asks you to provide a relevant query type — these queries are used at parameter resolution times. When we create our queries, they must conform to the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Entity​Query&lt;/code&gt; protocol, which has two required methods illustrated below:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// The required methods of EntityQuery&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; TaskQuery&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;EntityQuery &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; entities&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; identifiers&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [Entity.ID]) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; [TodoTask] {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; suggestedEntities&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; [TodoTask] {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;ol&gt;&lt;li&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;entities(for identifiers: [Entity.ID]) async throws -&amp;gt; [Todo​Task]&lt;/code&gt;: This method retrieves entities by their identifiers. Entities that do not match the identifiers will be skipped.&lt;/li&gt;&lt;li&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;func suggested​Entities() async throws -&amp;gt; [Todo​Task]&lt;/code&gt;: According to Apple, this method returns the initial results shown when a list of options backed by this query is presented.&lt;/li&gt;&lt;/ol&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;When requiring the user to choose one item from a collection of entities, Siri will repeatedly speak aloud asking the user to pick a selection without providing a list of options. In these situations, you should disambiguate between the items in your collection using &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;request​Disambiguation(among: \[Entity\])&lt;/code&gt;. This code will work as expected in the Shortcuts app.&lt;/p&gt;  &lt;/aside&gt;&lt;p&gt;Let’s continue — the aim of this intent is to provide a list of tasks for the user to choose from. Lets implement our query, starting with the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;suggested​Entities&lt;/code&gt; method.&lt;/p&gt;&lt;p&gt;Currently, our query object has no notion of the selected date. In order to bridge this gap, we have the handy property wrapper &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;@Intent​Parameter​Dependency&lt;/code&gt;, which allows us to obtain the intent and its properties. It can be used on objects which conform to &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Dynamic​Options​Provider&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Add the code below to the TaskQuery section above, and build:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; TaskQuery&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;EntityQuery &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  @IntentParameterDependency&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;CompleteTaskIntent&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      \.$date&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; completeTaskIntent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; taskManager: TaskManager = TaskManager.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;shared&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; calendar = Calendar.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;autoupdatingCurrent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; entities&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; identifiers&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [UUID]) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; [TodoTask] {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; try&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; suggestedEntities&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { task &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;          return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; identifiers.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;contains&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(task.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; suggestedEntities&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; [TodoTask] {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;      guard&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; selectedDate = completeTaskIntent?.date &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;          return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; startDate: Date = calendar.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;startOfDay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: selectedDate)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; endDate = calendar.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;date&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;byAdding&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;day&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: startDate)!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; foundTasks: [TodoTask] = &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; taskManager.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fetchTasks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; filteredTasks: [TodoTask] = foundTasks.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { task &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;          task.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;createDate&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &gt;= startDate &amp;#x26;&amp;#x26;  task.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;createDate&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x3C; endDate &amp;#x26;&amp;#x26; !task.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isComplete&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; filteredTasks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;For our querying logic within the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;suggested​Entites()&lt;/code&gt; method above, we ensure the selected date is present. If it isn’t present, we return an empty array. We then get the start of the selected date, and create the end date by adding one day to that. With this logic, we are trying to set up the window that queried tasks with fall within. Then, we also make sure the tasks returned have not already been completed, ensuring we have the most accurate data presented to the user. We then update the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;entities(for identifiers: [UUID])&lt;/code&gt; method. In this method, we use the &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;suggested​Entites&lt;/code&gt; to return the tasks that match the identifiers provided by the system.&lt;/p&gt;&lt;h3&gt;Adding a task&lt;/h3&gt;&lt;p&gt;At this point in the process, we haven’t created any tasks, so when we run the app or try to execute the intent, we won’t see any options provided.&lt;/p&gt;&lt;p&gt;Let’s change that! Run the app and tap on the plus button on the main screen. A modal view will present itself — add a task there. You should see the task presented on the list once added.&lt;/p&gt;&lt;div class=&quot;not-prose&quot;&gt; &lt;style&gt;astro-island,astro-slot,astro-static-slot{display:contents}&lt;/style&gt;&lt;script&gt;(()=&gt;{var l=(s,i,o)=&gt;{let r=async()=&gt;{await(await s())()},t=typeof i.value==&quot;object&quot;?i.value:void 0,c={rootMargin:t==null?void 0:t.rootMargin},n=new IntersectionObserver(e=&gt;{for(let a of e)if(a.isIntersecting){n.disconnect(),r();break}},c);for(let e of o.children)n.observe(e)};(self.Astro||(self.Astro={})).visible=l;window.dispatchEvent(new Event(&quot;astro:visible&quot;));})();;(()=&gt;{var A=Object.defineProperty;var g=(i,o,a)=&gt;o in i?A(i,o,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[o]=a;var d=(i,o,a)=&gt;g(i,typeof o!=&quot;symbol&quot;?o+&quot;&quot;:o,a);{let i={0:t=&gt;m(t),1:t=&gt;a(t),2:t=&gt;new RegExp(t),3:t=&gt;new Date(t),4:t=&gt;new Map(a(t)),5:t=&gt;new Set(a(t)),6:t=&gt;BigInt(t),7:t=&gt;new URL(t),8:t=&gt;new Uint8Array(t),9:t=&gt;new Uint16Array(t),10:t=&gt;new Uint32Array(t),11:t=&gt;1/0*t},o=t=&gt;{let[l,e]=t;return l in i?i[l](e):void 0},a=t=&gt;t.map(o),m=t=&gt;typeof t!=&quot;object&quot;||t===null?t:Object.fromEntries(Object.entries(t).map(([l,e])=&gt;[l,o(e)]));class y extends HTMLElement{constructor(){super(...arguments);d(this,&quot;Component&quot;);d(this,&quot;hydrator&quot;);d(this,&quot;hydrate&quot;,async()=&gt;{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest(&quot;astro-island[ssr]&quot;);if(e){e.addEventListener(&quot;astro:hydrate&quot;,this.hydrate,{once:!0});return}let c=this.querySelectorAll(&quot;astro-slot&quot;),n={},h=this.querySelectorAll(&quot;template[data-astro-template]&quot;);for(let r of h){let s=r.closest(this.tagName);s!=null&amp;&amp;s.isSameNode(this)&amp;&amp;(n[r.getAttribute(&quot;data-astro-template&quot;)||&quot;default&quot;]=r.innerHTML,r.remove())}for(let r of c){let s=r.closest(this.tagName);s!=null&amp;&amp;s.isSameNode(this)&amp;&amp;(n[r.getAttribute(&quot;name&quot;)||&quot;default&quot;]=r.innerHTML)}let p;try{p=this.hasAttribute(&quot;props&quot;)?m(JSON.parse(this.getAttribute(&quot;props&quot;))):{}}catch(r){let s=this.getAttribute(&quot;component-url&quot;)||&quot;&lt;unknown&gt;&quot;,v=this.getAttribute(&quot;component-export&quot;);throw v&amp;&amp;(s+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${s}`,this.getAttribute(&quot;props&quot;),r),r}let u;await this.hydrator(this)(this.Component,p,n,{client:this.getAttribute(&quot;client&quot;)}),this.removeAttribute(&quot;ssr&quot;),this.dispatchEvent(new CustomEvent(&quot;astro:hydrate&quot;))});d(this,&quot;unmount&quot;,()=&gt;{this.isConnected||this.dispatchEvent(new CustomEvent(&quot;astro:unmount&quot;))})}disconnectedCallback(){document.removeEventListener(&quot;astro:after-swap&quot;,this.unmount),document.addEventListener(&quot;astro:after-swap&quot;,this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute(&quot;await-children&quot;)||document.readyState===&quot;interactive&quot;||document.readyState===&quot;complete&quot;)this.childrenConnectedCallback();else{let e=()=&gt;{document.removeEventListener(&quot;DOMContentLoaded&quot;,e),c.disconnect(),this.childrenConnectedCallback()},c=new MutationObserver(()=&gt;{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&amp;&amp;this.lastChild.nodeValue===&quot;astro:end&quot;&amp;&amp;(this.lastChild.remove(),e())});c.observe(this,{childList:!0}),document.addEventListener(&quot;DOMContentLoaded&quot;,e)}}async childrenConnectedCallback(){let e=this.getAttribute(&quot;before-hydration-url&quot;);e&amp;&amp;await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute(&quot;opts&quot;)),c=this.getAttribute(&quot;client&quot;);if(Astro[c]===void 0){window.addEventListener(`astro:${c}`,()=&gt;this.start(),{once:!0});return}try{await Astro[c](async()=&gt;{let n=this.getAttribute(&quot;renderer-url&quot;),[h,{default:p}]=await Promise.all([import(this.getAttribute(&quot;component-url&quot;)),n?import(n):()=&gt;()=&gt;{}]),u=this.getAttribute(&quot;component-export&quot;)||&quot;default&quot;;if(!u.includes(&quot;.&quot;))this.Component=h[u];else{this.Component=h;for(let f of u.split(&quot;.&quot;))this.Component=this.Component[f]}return this.hydrator=p,this.hydrate},e,this)}catch(n){console.error(`[astro-island] Error hydrating ${this.getAttribute(&quot;component-url&quot;)}`,n)}}attributeChangedCallback(){this.hydrate()}}d(y,&quot;observedAttributes&quot;,[&quot;props&quot;]),customElements.get(&quot;astro-island&quot;)||customElements.define(&quot;astro-island&quot;,y)}})();&lt;/script&gt;&lt;astro-island uid=&quot;DGB93&quot; prefix=&quot;r0&quot; component-url=&quot;/opt/build/repo/embeds/ClientEmbed.tsx&quot; component-export=&quot;ClientEmbed&quot; renderer-url=&quot;@astrojs/react/client.js&quot; props=&quot;{&amp;quot;providerId&amp;quot;:[0,&amp;quot;youTube&amp;quot;],&amp;quot;wrapperClass&amp;quot;:[0,&amp;quot;aspect-video&amp;quot;],&amp;quot;youTubeId&amp;quot;:[0,&amp;quot;eMEw2h1NLHY&amp;quot;]}&quot; ssr=&quot;&quot; client=&quot;visible&quot; before-hydration-url=&quot;astro:scripts/before-hydration.js&quot; opts=&quot;{&amp;quot;name&amp;quot;:&amp;quot;ClientEmbed&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;rootMargin&amp;quot;:&amp;quot;400px&amp;quot;}}&quot; await-children=&quot;&quot;&gt;&lt;div class=&quot;aspect-video&quot;&gt;&lt;div data-testid=&quot;general-observer&quot; class=&quot;mdx-embed&quot;&gt;&lt;div style=&quot;height:0;width:100%&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--astro:end--&gt;&lt;/astro-island&gt; &lt;/div&gt;&lt;p&gt;Now that we’ve added a task, let’s execute our intent. Open the Shortcuts app and follow the video below to execute the intent:&lt;/p&gt;&lt;div class=&quot;not-prose&quot;&gt; &lt;astro-island uid=&quot;9j0ih&quot; prefix=&quot;r1&quot; component-url=&quot;/opt/build/repo/embeds/ClientEmbed.tsx&quot; component-export=&quot;ClientEmbed&quot; renderer-url=&quot;@astrojs/react/client.js&quot; props=&quot;{&amp;quot;providerId&amp;quot;:[0,&amp;quot;youTube&amp;quot;],&amp;quot;wrapperClass&amp;quot;:[0,&amp;quot;aspect-video&amp;quot;],&amp;quot;youTubeId&amp;quot;:[0,&amp;quot;2s6CQh0RhKE&amp;quot;]}&quot; ssr=&quot;&quot; client=&quot;visible&quot; before-hydration-url=&quot;astro:scripts/before-hydration.js&quot; opts=&quot;{&amp;quot;name&amp;quot;:&amp;quot;ClientEmbed&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;rootMargin&amp;quot;:&amp;quot;400px&amp;quot;}}&quot; await-children=&quot;&quot;&gt;&lt;div class=&quot;aspect-video&quot;&gt;&lt;div data-testid=&quot;general-observer&quot; class=&quot;mdx-embed&quot;&gt;&lt;div style=&quot;height:0;width:100%&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--astro:end--&gt;&lt;/astro-island&gt; &lt;/div&gt;&lt;p&gt;You may have noticed that tapping the option in the presented menu does not mark the task as complete in the app. If you take a look in the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Complete​Task​Intent.swift&lt;/code&gt; file within the perform method, you‘ll see we haven’t added any logic to be executed after the parameter resolution phase has taken place.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; perform&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; IntentResult {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;In the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Tasks&lt;/code&gt; folder, there is a file called &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Task​Manager&lt;/code&gt; that has the ability to add a task to the database. Add this as a property to this object. We will create a new instance of this object within our intent — in the context of Shortcuts and Siri, there is no way for us to inject it as a dependency. (In other contexts, such as within the widget or in the main application, we may be able to inject the &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Task​Manager&lt;/code&gt; as a dependency.) The intent file should now look like this.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; CompleteTaskIntent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AppIntent &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  @Parameter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(title: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Date&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; date: Date&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  @Parameter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(title: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Task&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; task: TodoTask&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; title: LocalizedStringResource = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Complete Task&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; taskManager = TaskManager.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;shared&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  @MainActor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; perform&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; IntentResult {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      taskManager.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;markTaskAsComplete&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: task)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Now, when you execute the shortcut and select a task once you return to the app, that task will be completed. To make our user experience a little nicer, lets add a view to confirm that the task has been marked completed.&lt;/p&gt;&lt;div class=&quot;not-prose&quot;&gt; &lt;astro-island uid=&quot;1aPhIK&quot; prefix=&quot;r2&quot; component-url=&quot;/opt/build/repo/embeds/ClientEmbed.tsx&quot; component-export=&quot;ClientEmbed&quot; renderer-url=&quot;@astrojs/react/client.js&quot; props=&quot;{&amp;quot;providerId&amp;quot;:[0,&amp;quot;youTube&amp;quot;],&amp;quot;wrapperClass&amp;quot;:[0,&amp;quot;aspect-video&amp;quot;],&amp;quot;youTubeId&amp;quot;:[0,&amp;quot;BReKXl8BO60&amp;quot;]}&quot; ssr=&quot;&quot; client=&quot;visible&quot; before-hydration-url=&quot;astro:scripts/before-hydration.js&quot; opts=&quot;{&amp;quot;name&amp;quot;:&amp;quot;ClientEmbed&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;rootMargin&amp;quot;:&amp;quot;400px&amp;quot;}}&quot; await-children=&quot;&quot;&gt;&lt;div class=&quot;aspect-video&quot;&gt;&lt;div data-testid=&quot;general-observer&quot; class=&quot;mdx-embed&quot;&gt;&lt;div style=&quot;height:0;width:100%&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--astro:end--&gt;&lt;/astro-island&gt; &lt;/div&gt;&lt;p&gt;To achieve this, lets create our confirmation view by adding this code to the intent file:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; successConfirmationView: &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; View {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  VStack&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      Image&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;systemName&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;checkmark.circle&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;          .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;foregroundStyle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;green&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;          .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;imageScale&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;large&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;          .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;font&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;largeTitle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Task Completed&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;To ensure our intent doesn’t crash, we will update our perform method return type to include &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Shows​Snippet​View&lt;/code&gt;:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@MainActor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; perform&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ShowsSnippetView {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    taskManager.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;markTaskAsComplete&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: task)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;view&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: successConfirmationView)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Now we can call the result method and pass the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;success​Confirmation​View&lt;/code&gt; as a parameter.&lt;/p&gt;&lt;p&gt;The app can now open once the task is executed. By default, the value of &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;open​App​When​Run&lt;/code&gt; is &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;false&lt;/code&gt; — if you’d like to open the app after the task has been executed, update this value to &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;true&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Wrapping up&lt;/h3&gt;&lt;p&gt;In this blog post, we’ve explored the intricacies of creating an App Intent for our simple to-do list application. Starting with the basics, we delved into how to define and handle parameters such as the date and task selection, and then integrated them into our app‘s data model. Our journey included conforming to the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;App​Intents&lt;/code&gt; protocol, dealing with the nuances of custom data types, and understanding the importance of entity queries in the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;App​Intents&lt;/code&gt; framework.&lt;/p&gt;&lt;p&gt;As we have seen, integrating App Intents requires a thoughtful approach to app architecture and a deep understanding of both the app‘s data model and the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;App​Intents&lt;/code&gt; framework. However, the payoff is a more streamlined and efficient user experience.&lt;/p&gt;&lt;p&gt;For those who are looking to further explore this topic or apply these concepts in their own projects, I encourage you to experiment with different types of intents and explore how they can be tailored to fit the unique requirements of your app. The possibilities are endless!&lt;/p&gt; </content:encoded><author>Ashli Rankin</author></item><item><title>Designing Mastodon’s reply safety features</title><link>https://lickability.com/blog/designing-mastodons-reply-safety-features/</link><guid isPermaLink="true">https://lickability.com/blog/designing-mastodons-reply-safety-features/</guid><description>Exploring the design process behind Mastodon’s latest trust and safety feature</description><pubDate>Thu, 07 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Lickability has been Mastodon’s design partner since 2021, when &lt;a href=&quot;https://lickability.com/blog/designing-mastodons-app/&quot;&gt;we helped design their first-party iOS app&lt;/a&gt; (and more recently, their Android app). In 1.0 for each of those apps, there have been the expected safety features — mute, block, and report. But those are very direct and technical tools. Don’t get us wrong, those features are critical to having some level of safety on any online platform. But their reach is limited to what you see, which, by definition, puts the onus on the person who needs it. If someone is bothering you, &lt;em&gt;you’re&lt;/em&gt; required to block or mute them.&lt;/p&gt;&lt;p&gt;With two million active users, Mastodon facilitates thousands of conversations regularly. We’d like to see each one be incredibly fruitful, but things are bound to go downhill for any number of reasons. Maybe someone discovers a post by following a hashtag and inappropriately replies with a familiar tone. Maybe they reply in a way that detracts from the main point. Maybe they didn’t realize they’re replying to a post from over a year ago. They might not be malicious, but their reply could feel unwelcome to the recipient all the same. Moreover, blocking/muting/reporting might not be the right “level” of response for this type of interaction.&lt;/p&gt;&lt;p&gt;There was certainly room for a different type of safety feature. We started by looking at other products that allow writing public replies and comments on content.&lt;/p&gt;&lt;h2&gt;Competitive research&lt;/h2&gt;&lt;p&gt;Back in 2019, which was roughly 400 years ago, &lt;a href=&quot;https://about.instagram.com/blog/announcements/instagrams-commitment-to-lead-fight-against-online-bullying&quot;&gt;Instagram announced an “intervention” feature&lt;/a&gt; that combined a prompt with a send delay to give you a chance to cancel sending a comment before it actually went through.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Comment warning on Instagram&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/96a01ad3b90f1ce3190d27a58b05e733ea2267a4-3120x3248.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/96a01ad3b90f1ce3190d27a58b05e733ea2267a4-3120x3248.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/96a01ad3b90f1ce3190d27a58b05e733ea2267a4-3120x3248.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/96a01ad3b90f1ce3190d27a58b05e733ea2267a4-3120x3248.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/96a01ad3b90f1ce3190d27a58b05e733ea2267a4-3120x3248.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/96a01ad3b90f1ce3190d27a58b05e733ea2267a4-3120x3248.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/96a01ad3b90f1ce3190d27a58b05e733ea2267a4-3120x3248.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/96a01ad3b90f1ce3190d27a58b05e733ea2267a4-3120x3248.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3120 3120w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/96a01ad3b90f1ce3190d27a58b05e733ea2267a4-3120x3248.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1666&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Credit: Instagram&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Twitter rolled out &lt;a href=&quot;https://twitter.com/Support/status/1257717113705414658&quot;&gt;a similar experiment&lt;/a&gt; that asked users to rethink sending a tweet “if it uses language that could be harmful.”&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Example prompt to revise a reply, with the headline “Want to review this before Tweeting?”. Below that is a preview of the reply that contains potentially harmful or offensive language and a link that reads, “Did we get this wrong?”. Along the bottom of the prompt are three buttons to “Tweet”, “Edit”, or “Delete”.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/cb001605135c2ab7284d4b933e0ddf83b59d734c-1200x1200.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w, https://cdn.sanity.io/images/nkt6o869/production/cb001605135c2ab7284d4b933e0ddf83b59d734c-1200x1200.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/cb001605135c2ab7284d4b933e0ddf83b59d734c-1200x1200.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=900 900w, https://cdn.sanity.io/images/nkt6o869/production/cb001605135c2ab7284d4b933e0ddf83b59d734c-1200x1200.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/cb001605135c2ab7284d4b933e0ddf83b59d734c-1200x1200.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200&quot; width=&quot;1200&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Credit: Twitter&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;And YouTube is currently testing &lt;a href=&quot;https://support.google.com/youtube/thread/160444193/new-channel-guidelines&quot;&gt;their own version of this prompt&lt;/a&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A flow chart of YouTube’s prompt in their mobile app. Tapping the comment field presents a sheet titled “Shirley Kim Guidelines” with the video creator’s profile photo. A speech bubble coming from the photo reads “Welcome! Please read my guidelines before you join the conversation. Have fun!” The guidelines are: “my preferred pronoun is “she/her,” “stick to the topic,” and “make this a fun place for everyone.” At the bottom of the sheet, the only button reads “Got it”.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/37849486e5129c65b5b498e359e2168e606a3405-766x492.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=192 192w, https://cdn.sanity.io/images/nkt6o869/production/37849486e5129c65b5b498e359e2168e606a3405-766x492.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=383 383w, https://cdn.sanity.io/images/nkt6o869/production/37849486e5129c65b5b498e359e2168e606a3405-766x492.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=575 575w, https://cdn.sanity.io/images/nkt6o869/production/37849486e5129c65b5b498e359e2168e606a3405-766x492.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=766 766w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/37849486e5129c65b5b498e359e2168e606a3405-766x492.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=766&quot; width=&quot;766&quot; height=&quot;492&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Credit: YouTube&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Twitter and Instagram both prompt after you’ve drafted a post that they find to be harmful, while YouTube (and &lt;a href=&quot;https://safety.twitch.tv/s/article/Chat-Tools?language=en_US#7ChannelRules&quot;&gt;Twitch&lt;/a&gt;, for that fact) prompts prior to writing anything — everyone gets the YouTube sheet the first time they comment on a channel.&lt;/p&gt;&lt;p&gt;Instagram’s prompt is the only one where the tone is centered around the product (“Keep Instagram a Supportive Place”) and not the interaction itself. It primarily depends on how much personal resonance the user feels with Instagram, the platform. Feel about that how you wish.&lt;/p&gt;&lt;h2&gt;Drafting our prompts&lt;/h2&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Three Figma frames. The left one is the post detail view for the Mastodon Android app. The middle one shows the view with a sheet overlaid. The sheet content: “Hello, new connection! Looks like you&amp;#x27;re about to reply to someone who isn&amp;#x27;t a mutual connection yet. Let&amp;#x27;s make a great first impression. Rule 1: Respect boundaries. Keep in mind they may not know you yet. Friendly and respectful communication goes a long way! Rule 2: Stay on topic. Make sure your reply is relevant to their post. Avoid unsolicited advice or personal remarks. Rule 3: Be kind. A little kindness can make a big difference. Avoid harsh language or criticism. Rule 4: Listen and learn. Be open to feedback. Everyone&amp;#x27;s approach to conversation is different.” Two buttons at the bottom: “Got it” and “Don’t remind me again”. The right Figma frame shows a similar sheet with this content: “This post is over 3 months old. You can still reply, but it may no longer be relevant.” The same two buttons at the bottom.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e9b1b11d8278349adc10fec9ab561aa977632f4c-2634x1824.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/e9b1b11d8278349adc10fec9ab561aa977632f4c-2634x1824.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/e9b1b11d8278349adc10fec9ab561aa977632f4c-2634x1824.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/e9b1b11d8278349adc10fec9ab561aa977632f4c-2634x1824.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/e9b1b11d8278349adc10fec9ab561aa977632f4c-2634x1824.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/e9b1b11d8278349adc10fec9ab561aa977632f4c-2634x1824.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/e9b1b11d8278349adc10fec9ab561aa977632f4c-2634x1824.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2634 2634w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e9b1b11d8278349adc10fec9ab561aa977632f4c-2634x1824.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1108&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;With these references in mind, we started drafting our own prompts. With apologies to Gavin Nelson for being my placeholder user in our initial design concepts: &lt;/p&gt;&lt;p&gt;We liked YouTube’s approach for a few reasons:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;You haven’t drafted anything yet, so if you decide against writing a reply altogether, you haven’t wasted any time.&lt;/li&gt;&lt;li&gt;The primary action is, in both cases, to proceed to commenting. &lt;strong&gt;We don’t want to hinder conversation, we just want to make the conversation higher quality.&lt;/strong&gt; In contrast, the action Instagram’s prompt wants you to take is “don’t send this after all,” and Twitter weights their three actions equally.&lt;/li&gt;&lt;li&gt;We wanted to move fast, and since this prompt is happening entirely on device, it does not involve any sort of tone analysis that might tax the hardware.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;But this wasn’t &lt;em&gt;exactly&lt;/em&gt; right. The copy for the old post prompt didn’t sit right with me. It tells the recipient about the logic gate (&lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;post.months​Since​Published &amp;gt;= 3 ? show​Prompt() : simply​Dont()&lt;/code&gt;) and makes them fill in the blanks. When showing the draft to a few people, we got feedback that the non-mutual prompt is just a wall of text and doesn’t provide any color on who you’re about to reply to. Our next iteration shortened the rule list and included an abbreviated user profile as an inset card.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Say hi to someone new! Before you reply to someone who isn&amp;#x27;t a mutual connection yet, a quick reminder to make a great first impression. A slightly darker-tinted card contains the original author’s photo, username, follower count, and two lines of their bio. Under the card: Rule 1: Stay respectful and relevant. Ensure your reply is courteous and on-topic. Rule 2: Embrace kindness. A positive tone is always appreciated. Rule 3: Be open. Everyone’s conversation style is unique. Be ready to adapt.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/231c0d312d0490279f0522744fc9f55bd76d7a78-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/231c0d312d0490279f0522744fc9f55bd76d7a78-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/231c0d312d0490279f0522744fc9f55bd76d7a78-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/231c0d312d0490279f0522744fc9f55bd76d7a78-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/231c0d312d0490279f0522744fc9f55bd76d7a78-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;For final changes, we made the profile photo larger (you should know who you’re interacting with!) and included four lines of text: the first two lines of their bio, and the first two custom fields (key-value pairs). We’ve noticed that people tend to put pronouns or their job title in their custom fields, which gives the prompt recipient some background to avoid potentially misgendering someone or explaining something to an industry expert.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Two phones side-by-side showing the different prompt designs. The left one reads: “This post is 5 months old. You can still reply, but it may no longer be relevant.” and the right one reads: “Hello, new connection! Looks like you&amp;#x27;re about to reply to someone who isn&amp;#x27;t a mutual connection yet. Let&amp;#x27;s make a great first impression.” A card includes their profile information and two extra rows: “Pronouns: They/Them” and “Blog: example.com”. The rules remain unchanged from the previous draft. Both use the same buttons as previous designs.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c2b77b6b1bf8d742adb914a1dfb4075a1e028255-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/c2b77b6b1bf8d742adb914a1dfb4075a1e028255-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/c2b77b6b1bf8d742adb914a1dfb4075a1e028255-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/c2b77b6b1bf8d742adb914a1dfb4075a1e028255-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c2b77b6b1bf8d742adb914a1dfb4075a1e028255-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Our final design.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h2&gt;Design learnings&lt;/h2&gt;&lt;p&gt;The announcement and rollout went well, with the people who this was designed to help expressing their interest in this feature. It was picked up by &lt;a href=&quot;https://www.theverge.com/2023/11/22/23972516/mastodon-tries-to-mediate-awkward-replies-before-they-happen&quot;&gt;the&lt;/a&gt;&lt;a href=&quot;https://techcrunch.com/2023/11/22/mastodon-tackles-the-problem-of-reply-guys-with-its-latest-feature/&quot;&gt;media&lt;/a&gt; and was a gay ol’ time.&lt;/p&gt;&lt;p&gt;There were also Opinions in the replies to &lt;a href=&quot;https://mastodon.social/@Mastodon/111454711657717967&quot;&gt;the announcement post&lt;/a&gt;, to be sure. A few people mentioned an interstitial prompt is poor UX and increases cognitive load &amp;amp; friction. But when the feature goal is safety, we’re not really designing for people to cruise through the product — we’re designing to mitigate potential damage. Making things frictionless and letting &lt;a href=&quot;https://sensible.com/dont-make-me-think/&quot;&gt;people think less&lt;/a&gt; would be counter to what we’re trying to achieve.&lt;/p&gt;&lt;p&gt;That said, the design above likely isn’t the &lt;em&gt;final&lt;/em&gt; final iteration that will stick around for the long haul. We’re still keeping an eye on how people interact with this feature and making changes accordingly.&lt;/p&gt;&lt;p&gt;Successful UX design has often been characterized as design that disappears and gets out of the way to let the user do their thing. It’s partially why Mac OS X’s Aqua interface got toned down over the years from sharp pinstripes to gray gradients:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;The same window on the first version of Mac OS X vs the same window under macOS Catalina. The vibrant blue controls and pinstripes have been traded for subtle gray shades and hints of accent color.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b60e246e28bc2035a8163b022af675b6a0c070f6-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/b60e246e28bc2035a8163b022af675b6a0c070f6-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/b60e246e28bc2035a8163b022af675b6a0c070f6-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/b60e246e28bc2035a8163b022af675b6a0c070f6-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b60e246e28bc2035a8163b022af675b6a0c070f6-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Credit: 512pixels.net&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;But thoughtful UX design is willing to step out from the shadows and make itself known if it might benefit the greater good.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Need thoughtful product design for your app? &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Get in touch!&lt;/a&gt;&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Sam Henri Gold</author></item><item><title>Creating your first app shortcut</title><link>https://lickability.com/blog/creating-your-first-app-shortcut/</link><guid isPermaLink="true">https://lickability.com/blog/creating-your-first-app-shortcut/</guid><description>How to use Apple&apos;s new AppIntents framework</description><pubDate>Thu, 02 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In iOS 16, Apple released the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;App​Intents&lt;/code&gt; framework to allow developers to more easily integrate their app actions with the system. App actions can be used in various parts of the system like the Shortcuts app, Siri, and Spotlight. Before the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;App​Intents&lt;/code&gt; framework was released, we used &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Siri​Kit&lt;/code&gt; to communicate our actions to the system — but this had a higher barrier to entry due to little documentation &amp;amp; resources.&lt;/p&gt;&lt;p&gt;One of my favorite features of creating app actions with &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;App​Intents&lt;/code&gt; is the ease of prototyping. I can go from an idea to a functioning action in under an hour. With this guide, I‘ll walk you through how to create your first app action for the Shortcuts app.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;The action we create will be available to the system when our app is first installed, and will be available to Siri immediately as well. But this guide does not focus on the interactions between the action and Siri — we‘ll just be focusing on how the user can use your action with Shortcuts.&lt;/p&gt;  &lt;/aside&gt;&lt;p&gt;Before we begin, let’s look at what we’ll need to do to create a simple shortcut:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Conform to the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;App​Intent&lt;/code&gt; &lt;a href=&quot;https://developer.apple.com/documentation/appintents/appintent&quot;&gt;protocol&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Add a description&lt;/li&gt;&lt;li&gt;Add your parameter(s)&lt;/li&gt;&lt;li&gt;Handle user input in the perform method&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Conform to the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;App​Intent&lt;/code&gt; protocol&lt;/h3&gt;&lt;p&gt;Start by conforming to the AppIntents protocol. This can be done by giving the action a title, then adding the perform method.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AppIntents&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AddTaskIntent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AppIntent &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; title: LocalizedStringResource = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Add task&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; perform&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; IntentResult {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d3742b4c1c39b9129df6d1119fb904fd4780742e-600x1300.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w, https://cdn.sanity.io/images/nkt6o869/production/d3742b4c1c39b9129df6d1119fb904fd4780742e-600x1300.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d3742b4c1c39b9129df6d1119fb904fd4780742e-600x1300.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600&quot; width=&quot;600&quot; height=&quot;1300&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;title&lt;/code&gt;: The title of the shortcut to be displayed in the shortcuts app. &lt;code index=&quot;2&quot; isInline=&quot;true&quot;&gt;perform()&lt;/code&gt;: The perform method carries out the intent action. &lt;strong&gt;For example:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;It can carry out any persistence after you’ve acted on the input the user provides.&lt;/li&gt;&lt;li&gt;It disambiguates responses to the user if the system finds ambiguity due to multiple values.&lt;/li&gt;&lt;li&gt;It displays views which are shown to the user once an action has been carried out.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;If you want to open the app once the action has been executed, set the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;open​App​When​Run&lt;/code&gt; property to true.&lt;/p&gt;&lt;p&gt;First task completed!&lt;/p&gt;&lt;h3&gt;Add a description&lt;/h3&gt;&lt;p&gt;A description describes the purpose of the intent — it is an optional property, but we‘re going to add one here as the next step.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AppIntents&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AddTaskIntent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AppIntent &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; title: LocalizedStringResource = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Add task&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; description: IntentDescription? = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;This action allows you to add a new task to your to-do list &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; perform&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; IntentResult {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/166f82acc45719c18599d9c16e2b253f440eb8d0-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=323 323w, https://cdn.sanity.io/images/nkt6o869/production/166f82acc45719c18599d9c16e2b253f440eb8d0-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=645 645w, https://cdn.sanity.io/images/nkt6o869/production/166f82acc45719c18599d9c16e2b253f440eb8d0-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=968 968w, https://cdn.sanity.io/images/nkt6o869/production/166f82acc45719c18599d9c16e2b253f440eb8d0-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1290 1290w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/166f82acc45719c18599d9c16e2b253f440eb8d0-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1290&quot; width=&quot;1290&quot; height=&quot;2796&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Add parameter(s)&lt;/h3&gt;&lt;p&gt;In this example app, we want the user to add a task by typing it into a text box or speaking to Siri. To make that happen, we’ll add a parameter to capture user input. Parameters can take many types, from primitives to more complex types such as &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Intent​Person&lt;/code&gt;. For a comprehensive list of possible parameter types, refer to &lt;a href=&quot;https://developer.apple.com/documentation/appintents/intentparameter?language=_6&quot;&gt;this documentation page&lt;/a&gt;.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Foundation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AppIntents&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AddTaskIntent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AppIntent &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  @Parameter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(title: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Task&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; task: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; title: LocalizedStringResource = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Add task&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; description: IntentDescription? = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;This action allows you to add a new task to your to-do list &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; perform&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; IntentResult {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;We can add other properties to the parameter property wrapper, such as:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;description&lt;/code&gt; describes what the property represents. The description added to your parameters will be displayed in the description of the app action.&lt;/li&gt;&lt;li&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;input​Options&lt;/code&gt; describes how we’d like the input to be styled when using the Shortcuts App. It has properties such as &lt;code index=&quot;2&quot; isInline=&quot;true&quot;&gt;keyboard​Type&lt;/code&gt;, &lt;code index=&quot;4&quot; isInline=&quot;true&quot;&gt;capitalization​Type&lt;/code&gt;, &lt;code index=&quot;6&quot; isInline=&quot;true&quot;&gt;multiline&lt;/code&gt; , &lt;code index=&quot;8&quot; isInline=&quot;true&quot;&gt;autocorrect&lt;/code&gt; and others.&lt;/li&gt;&lt;li&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;request​Value​Dialog&lt;/code&gt; describes the exact phrase we’d like to be spoken in an audio context.&lt;/li&gt;&lt;li&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;request​Disambiguation​Dialog&lt;/code&gt; describes the phrase we’d like spoken in case of multiple values — to disambiguate between them, Siri will ask the user to choose between the various values.&lt;/li&gt;&lt;/ul&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/52e9ef39a7f95e0f0d9b2706c8d5245f05a4ae20-600x1300.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w, https://cdn.sanity.io/images/nkt6o869/production/52e9ef39a7f95e0f0d9b2706c8d5245f05a4ae20-600x1300.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/52e9ef39a7f95e0f0d9b2706c8d5245f05a4ae20-600x1300.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600&quot; width=&quot;600&quot; height=&quot;1300&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Our parameter was added as a row below the title. We can make this nicer by adding a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Parameter​Summary&lt;/code&gt;. A &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Parameter​Summary&lt;/code&gt; is a sentence that represents the intent and its parameters. &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2022-10032/?time=501&quot;&gt;Apple recommends&lt;/a&gt; that we provide parameter summaries to make the user interface more streamlined.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Foundation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AppIntents&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AddTaskIntent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AppIntent &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  @Parameter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(title: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Task&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; task: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; title: LocalizedStringResource = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Add task&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; description: IntentDescription? = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;This action allows you to add a new task to your to-do list &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; parameterSummary: &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ParameterSummary {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    Summary&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Add &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;\.$task&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; perform&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; IntentResult {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b6a7674ee95dcde6f620861b83a747f95ffb892a-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=323 323w, https://cdn.sanity.io/images/nkt6o869/production/b6a7674ee95dcde6f620861b83a747f95ffb892a-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=645 645w, https://cdn.sanity.io/images/nkt6o869/production/b6a7674ee95dcde6f620861b83a747f95ffb892a-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=968 968w, https://cdn.sanity.io/images/nkt6o869/production/b6a7674ee95dcde6f620861b83a747f95ffb892a-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1290 1290w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b6a7674ee95dcde6f620861b83a747f95ffb892a-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1290&quot; width=&quot;1290&quot; height=&quot;2796&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;In this example, if you run the shortcut, a text field will be displayed. Tapping “done” dismisses it, but no further interaction occurs.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/1f282b52863dd5f3b93ba71b4e4dda25f5774339-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=323 323w, https://cdn.sanity.io/images/nkt6o869/production/1f282b52863dd5f3b93ba71b4e4dda25f5774339-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=645 645w, https://cdn.sanity.io/images/nkt6o869/production/1f282b52863dd5f3b93ba71b4e4dda25f5774339-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=968 968w, https://cdn.sanity.io/images/nkt6o869/production/1f282b52863dd5f3b93ba71b4e4dda25f5774339-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1290 1290w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/1f282b52863dd5f3b93ba71b4e4dda25f5774339-1290x2796.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1290&quot; width=&quot;1290&quot; height=&quot;2796&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;The system treats your parameter as required or not based on the inclusion of &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;?&lt;/code&gt;. If you don’t add a &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;?&lt;/code&gt; mark, the system prompts the user for input — with the &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;?&lt;/code&gt; mark, the system would continue the execution of the action. If you’d like to request a value for the parameter, use &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;.needs​Value​Error()&lt;/code&gt; with the dialog you’d like the system to use to prompt the user.&lt;/p&gt;&lt;blockquote&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;needs​Value​Error(_:)&lt;br&gt;&lt;/code&gt;Returns a &lt;code index=&quot;2&quot; isInline=&quot;true&quot;&gt;restart​Perform&lt;/code&gt; error with context to request a value from the user for this parameter and re-perform the intent with the new values.&lt;/blockquote&gt;&lt;p&gt;— &lt;a href=&quot;https://developer.apple.com/documentation/appintents/intentparameter/needsvalueerror(_:)?language=_5&quot;&gt;Apple&lt;/a&gt;&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Foundation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AppIntents&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AddTaskIntent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AppIntent &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  @Parameter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(title: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Task&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; task: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; title: LocalizedStringResource = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Add task&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; description: IntentDescription? = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;This action allows you to add a new task to your to-do list &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; parameterSummary: &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ParameterSummary {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    Summary&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Add &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;\.$task&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; perform&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; IntentResult {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    guard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; !task.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isEmpty&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;      throw&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; $task.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;needsValueError&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;What task value would you like to add?&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6ab0cf2c05f50accbe9370cccd1c8d2a356ef8f4-1170x2532.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=293 293w, https://cdn.sanity.io/images/nkt6o869/production/6ab0cf2c05f50accbe9370cccd1c8d2a356ef8f4-1170x2532.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=585 585w, https://cdn.sanity.io/images/nkt6o869/production/6ab0cf2c05f50accbe9370cccd1c8d2a356ef8f4-1170x2532.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=878 878w, https://cdn.sanity.io/images/nkt6o869/production/6ab0cf2c05f50accbe9370cccd1c8d2a356ef8f4-1170x2532.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1170 1170w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6ab0cf2c05f50accbe9370cccd1c8d2a356ef8f4-1170x2532.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1170&quot; width=&quot;1170&quot; height=&quot;2532&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Handle user input in the perform method&lt;/h3&gt;&lt;p&gt;After establishing what user input you expect, you need to handle what happens when the action is performed.&lt;/p&gt;&lt;p&gt;In this example app, there is a need to persist the input in a task that will be stored locally on the device. In this perform method, the task is created and then persisted. Afterward, a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; view is displayed on successful persistence.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Foundation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AppIntents&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; SwiftUI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AddTaskIntent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AppIntent &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  @Parameter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(title: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Task&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; task: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; title: LocalizedStringResource = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Add task&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; description: IntentDescription? = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;This action allows you to add a new task to your to-do list &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; parameterSummary: &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ParameterSummary {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    Summary&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Add &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;\.$task&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; persistenceController: PersistenceController = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;shared&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  @MainActor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; perform&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; throws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&gt; &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ShowsSnippetView {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; persistenceController.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;addTask&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: task, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;createDate&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;view&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Task added successfully&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e1feafa42034933c7de88cde39c409972a3b1212-600x1300.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w, https://cdn.sanity.io/images/nkt6o869/production/e1feafa42034933c7de88cde39c409972a3b1212-600x1300.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e1feafa42034933c7de88cde39c409972a3b1212-600x1300.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600&quot; width=&quot;600&quot; height=&quot;1300&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;The perform method is executed when the system has resolved all the parameters your code needs to successfully execute its functionality. When implementing logic in the perform method, the logic you add must perform the necessary work that returns the value to the system. This result could include a value that a shortcut can use in subsequent connected actions, dialogue to display or announce, and a &lt;a href=&quot;https://developer.apple.com/documentation/SwiftUI&quot;&gt;SwiftUI&lt;/a&gt; snippet view, among other things. If it doesn’t make sense for your intent to return a concrete result, return &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;.result()&lt;/code&gt; to tell the system the intent is complete.&lt;/p&gt;&lt;blockquote class=&quot;quote-block&quot;&gt; &lt;p class=&quot;quote-text&quot;&gt;If your intent manipulates the app’s user interface, annotate perform() with @MainActor to make sure the function executes on the main queue. &lt;/p&gt; &lt;a href=&quot;https://developer.apple.com/documentation/appintents/providing-your-app-s-capabilities-to-system-services#Perform-the-action-for-your-intent&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; &lt;cite class=&quot;quote-author&quot;&gt;Apple&lt;/cite&gt; &lt;/a&gt; &lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; When we are specifying values to return from the perform method:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;If you are returning a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Swift​UI&lt;/code&gt; View, specify your return type as &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Shows​Snippet​View&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;If you are a returning value from the perform method, specify &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Returns​Value&amp;lt;T&amp;gt;&lt;/code&gt;, and specify the type you wish to return in parentheses. If you provide this value without the type, it will crash your app.&lt;/li&gt;&lt;li&gt;If you would like Siri to speak the output, return &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Provides​Dialog&lt;/code&gt; specifying what output Siri should speak.&lt;/li&gt;&lt;li&gt;If you want to return multiple types of output, specify each type separated by &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;&amp;amp;&lt;/code&gt;. For example: &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Shows​Snippet​View &amp;amp; Provides​Dialog &amp;amp; Returns​Value&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Now that we‘ve completed all the steps, we have the scaffolding for an intent! Using this knowledge, we can also build more complex use cases — we’ll take a look at that in &lt;a href=&quot;/blog/app-intents&quot; title=&quot;Mastering App Intents: Querying Made Easy&quot; data-astro-cid-okzjwv6e&gt;this post&lt;/a&gt; .&lt;/p&gt; </content:encoded><author>Ashli Rankin</author></item><item><title>Useful tips for implementing TipKit</title><link>https://lickability.com/blog/useful-tips-for-implementing-tipkit/</link><guid isPermaLink="true">https://lickability.com/blog/useful-tips-for-implementing-tipkit/</guid><description>A new framework for teaching users about your app</description><pubDate>Thu, 19 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With the launch of iOS 17, I was excited to take advantage of Apple‘s new framework &lt;a href=&quot;https://developer.apple.com/documentation/tipkit&quot;&gt;TipKit&lt;/a&gt;, and it just so happened I need to implement an onboarding style tip system for a client at the same time. From everything I’ve learned so far, here are my 5 tips for using TipKit.&lt;/p&gt;&lt;h3&gt;Tip 1: Customize TipView‘s background, corner radius, or image size&lt;/h3&gt;&lt;p&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Tip​View&lt;/code&gt; has many of its own view modifiers for customizing how it displays. One of the first gotchas I ran into was trying to change the background color of an inline tip. Normally, you’d use a view modifier like &lt;code index=&quot;2&quot; isInline=&quot;true&quot;&gt;.foreground​Color&lt;/code&gt; (which has been replaced by &lt;code index=&quot;4&quot; isInline=&quot;true&quot;&gt;.foreground​Style&lt;/code&gt;) to style the background. Unfortunately, that doesn’t seem to work here — I quickly discovered that you have to use &lt;code index=&quot;6&quot; isInline=&quot;true&quot;&gt;.tip​Background&lt;/code&gt; or &lt;code index=&quot;8&quot; isInline=&quot;true&quot;&gt;.tip​View​Style&lt;/code&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9b17e441cc047943f9c0aec57bf1a3292dfe6a11-1179x796.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=295 295w, https://cdn.sanity.io/images/nkt6o869/production/9b17e441cc047943f9c0aec57bf1a3292dfe6a11-1179x796.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=590 590w, https://cdn.sanity.io/images/nkt6o869/production/9b17e441cc047943f9c0aec57bf1a3292dfe6a11-1179x796.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=884 884w, https://cdn.sanity.io/images/nkt6o869/production/9b17e441cc047943f9c0aec57bf1a3292dfe6a11-1179x796.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1179 1179w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9b17e441cc047943f9c0aec57bf1a3292dfe6a11-1179x796.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1179&quot; width=&quot;1179&quot; height=&quot;796&quot;/&gt;  &lt;/figure&gt; &lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;TipView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(tip)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tipBackground&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;blue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;opacity&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.25&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;As of right now, the tip specific view modifiers for &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Tip​View&lt;/code&gt; are: &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;.tip​Image​Size&lt;/code&gt;, &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;.tip​Corner​Radius&lt;/code&gt;, &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;.tip​Background&lt;/code&gt;, and &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;.tip​View​Style&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Tip 2: Customize the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; of the tip directly in the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Tip&lt;/code&gt; itself&lt;/h3&gt;&lt;p&gt;Interestingly enough, to customize things about the text of the tips, you apply the view modifiers directly to the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; that is returned as part of conforming to the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Tip&lt;/code&gt; protocol. Any modifier that customizes &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; while returning a &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt; will work here e.g. &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;.foreground​Style&lt;/code&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d2ce8c4db82092c8d3df0a4cd73e173af487245a-1179x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=295 295w, https://cdn.sanity.io/images/nkt6o869/production/d2ce8c4db82092c8d3df0a4cd73e173af487245a-1179x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=590 590w, https://cdn.sanity.io/images/nkt6o869/production/d2ce8c4db82092c8d3df0a4cd73e173af487245a-1179x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=884 884w, https://cdn.sanity.io/images/nkt6o869/production/d2ce8c4db82092c8d3df0a4cd73e173af487245a-1179x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1179 1179w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d2ce8c4db82092c8d3df0a4cd73e173af487245a-1179x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1179&quot; width=&quot;1179&quot; height=&quot;780&quot;/&gt;  &lt;/figure&gt; &lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ExampleTip&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Tip &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // MARK: - Tip&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; title: Text {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Here is a tip&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;foregroundStyle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;red&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; message: Text? {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Tap &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Image&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;systemName&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;ellipsis.circle&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; to explore other ways to join.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;h3&gt;Tip 3: Customize the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Image&lt;/code&gt; of the tip with standard view modifiers&lt;/h3&gt;&lt;p&gt;Unlike &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Text&lt;/code&gt;, in order to customize the color of the icon used in the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Tip​View&lt;/code&gt;, you can use the common view modifiers like &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;.tint&lt;/code&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/86996b7ab5d9adf6d6634499dffbb618f5587171-1172x778.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=293 293w, https://cdn.sanity.io/images/nkt6o869/production/86996b7ab5d9adf6d6634499dffbb618f5587171-1172x778.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=586 586w, https://cdn.sanity.io/images/nkt6o869/production/86996b7ab5d9adf6d6634499dffbb618f5587171-1172x778.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=879 879w, https://cdn.sanity.io/images/nkt6o869/production/86996b7ab5d9adf6d6634499dffbb618f5587171-1172x778.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1172 1172w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/86996b7ab5d9adf6d6634499dffbb618f5587171-1172x778.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1172&quot; width=&quot;1172&quot; height=&quot;778&quot;/&gt;  &lt;/figure&gt; &lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;TipView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(tip)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pink&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;h3&gt;Tip 4: You can only configure &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Tips.configure()&lt;/code&gt; once per life cycle&lt;/h3&gt;&lt;p&gt;Apple has provided ways to tell TipKit to &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;.show​All​Tips​For​Testing()&lt;/code&gt;, &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;.show​Tips​For​Testing([Example​Tip.self, Example​Tip2.self])&lt;/code&gt;, &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;.hide​Tips​For​Testing([Example​Tip.self, Example​Tip2.self])&lt;/code&gt;, &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;.hide​All​Tips​For​Testing()&lt;/code&gt;, and &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;.reset​Datastore()&lt;/code&gt;. You need to call these before you call &lt;code index=&quot;11&quot; isInline=&quot;true&quot;&gt;Tips.configure()&lt;/code&gt; — and based on the docs, you should call that on startup. From my tests, any additional calls after that first &lt;code index=&quot;13&quot; isInline=&quot;true&quot;&gt;Tips.configure()&lt;/code&gt; will not result in changes.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; TipDebuggingApp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;App &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; body: &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Scene {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        WindowGroup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;            ContentView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;                    try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? Tips.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;resetDatastore&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;                    try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? Tips.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;configure&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;h3&gt;Tip 5: Build a debug or testing menu for tips in your app&lt;/h3&gt;&lt;p&gt;In my specific use case, I needed to build a special debugging tips menu to allow testers to play with how the tips would appear in the app (e.g. inline vs. popover). It took me a couple tries to get this working well.&lt;/p&gt;&lt;p&gt;I found it more reliable to call &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Tips.reset​Datastore()&lt;/code&gt; instead of trying to show and hide specific tips with &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;.show​Tips​For​Testing([Example​Tip.self, Example​Tip2.self])&lt;/code&gt; and &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;.hide​Tips​For​Testing([Example​Tip.self, Example​Tip2.self])&lt;/code&gt;.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; TipDebuggingApp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;App &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; body: &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Scene {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        WindowGroup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;            ContentView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;                    try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? Tips.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;resetDatastore&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;                    try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? Tips.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;configure&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;I then set up a view that would allow you to select which tip would be shown, and whether it‘s shown inline or as a popover.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; TipDebugView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;View &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    @AppStorage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(AppStorageKey.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;showTipsInPopover&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; showTipsInPopover: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Bool&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    @State&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; selection: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&gt; = []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; tips: [&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Tip] = [&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ExampleTip&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ExampleTip2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ExampleTip3&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; body: &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; View {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        VStack&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;            List&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(tips, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: \.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;selection&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: $selection) { tip &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                tip.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;title&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;environment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(\.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;editMode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;constant&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(EditMode.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;active&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;navigationTitle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Tips&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;onChange&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;of&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: selection) { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, newValue &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                UserDefaults.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;standard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Array&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(newValue), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: AppStorageKey.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;selectedTips&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;onAppear&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;                if&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; selection = UserDefaults.standard.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: AppStorageKey.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;selectedTips&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) as? [&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;] {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;                    self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;selection&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(selection)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;            Toggle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;isOn&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: $showTipsInPopover) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;                Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Display relevant tips as popovers?&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bold&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;padding&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;horizontal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, Grid.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;medium&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;            Spacer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;            Spacer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;background&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Color&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;uiColor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;systemGroupedBackground&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Because I used &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Tips.reset​Datastore()&lt;/code&gt;, any and all of my tips would show if they didn‘t have a rule customizing how they’re presented. I did want this behavior for testing, but only when a tip was selected. The solution for this is uglier code than I would have liked, but it work well from a user standpoint.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; tipSelections = UserDefaults.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;standard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: AppStorageKey.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;selectedTips&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) as? [&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; !showTipsInPopover {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; tip1 = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ExampleTip1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; tipSelections?.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;contains&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;where&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: { &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;$0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; == tip1.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; }) == &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        TipView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(tip1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; tipSelections = UserDefaults.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;standard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: AppStorageKey.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;selectedTips&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) as? [&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; tip1 = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ExampleTip&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; UserDefaults.standard.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: AppStorageKey.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;showTipsInPopover&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), tipSelections?.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;contains&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;where&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: { &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;$0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; == tip1.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; }) == &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    Button&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Example button&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;        // I perform some action.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;popoverTip&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(tip)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    Button&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Example button&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;        // I perform some action.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;I’ve provided a sample project of the debugging menu &lt;a href=&quot;https://github.com/Lickability/tip-kit-debugging-demo&quot;&gt;here&lt;/a&gt;, along with a video of the debugging.&lt;/p&gt;&lt;div class=&quot;not-prose&quot;&gt; &lt;style&gt;astro-island,astro-slot,astro-static-slot{display:contents}&lt;/style&gt;&lt;script&gt;(()=&gt;{var l=(s,i,o)=&gt;{let r=async()=&gt;{await(await s())()},t=typeof i.value==&quot;object&quot;?i.value:void 0,c={rootMargin:t==null?void 0:t.rootMargin},n=new IntersectionObserver(e=&gt;{for(let a of e)if(a.isIntersecting){n.disconnect(),r();break}},c);for(let e of o.children)n.observe(e)};(self.Astro||(self.Astro={})).visible=l;window.dispatchEvent(new Event(&quot;astro:visible&quot;));})();;(()=&gt;{var A=Object.defineProperty;var g=(i,o,a)=&gt;o in i?A(i,o,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[o]=a;var d=(i,o,a)=&gt;g(i,typeof o!=&quot;symbol&quot;?o+&quot;&quot;:o,a);{let i={0:t=&gt;m(t),1:t=&gt;a(t),2:t=&gt;new RegExp(t),3:t=&gt;new Date(t),4:t=&gt;new Map(a(t)),5:t=&gt;new Set(a(t)),6:t=&gt;BigInt(t),7:t=&gt;new URL(t),8:t=&gt;new Uint8Array(t),9:t=&gt;new Uint16Array(t),10:t=&gt;new Uint32Array(t),11:t=&gt;1/0*t},o=t=&gt;{let[l,e]=t;return l in i?i[l](e):void 0},a=t=&gt;t.map(o),m=t=&gt;typeof t!=&quot;object&quot;||t===null?t:Object.fromEntries(Object.entries(t).map(([l,e])=&gt;[l,o(e)]));class y extends HTMLElement{constructor(){super(...arguments);d(this,&quot;Component&quot;);d(this,&quot;hydrator&quot;);d(this,&quot;hydrate&quot;,async()=&gt;{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest(&quot;astro-island[ssr]&quot;);if(e){e.addEventListener(&quot;astro:hydrate&quot;,this.hydrate,{once:!0});return}let c=this.querySelectorAll(&quot;astro-slot&quot;),n={},h=this.querySelectorAll(&quot;template[data-astro-template]&quot;);for(let r of h){let s=r.closest(this.tagName);s!=null&amp;&amp;s.isSameNode(this)&amp;&amp;(n[r.getAttribute(&quot;data-astro-template&quot;)||&quot;default&quot;]=r.innerHTML,r.remove())}for(let r of c){let s=r.closest(this.tagName);s!=null&amp;&amp;s.isSameNode(this)&amp;&amp;(n[r.getAttribute(&quot;name&quot;)||&quot;default&quot;]=r.innerHTML)}let p;try{p=this.hasAttribute(&quot;props&quot;)?m(JSON.parse(this.getAttribute(&quot;props&quot;))):{}}catch(r){let s=this.getAttribute(&quot;component-url&quot;)||&quot;&lt;unknown&gt;&quot;,v=this.getAttribute(&quot;component-export&quot;);throw v&amp;&amp;(s+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${s}`,this.getAttribute(&quot;props&quot;),r),r}let u;await this.hydrator(this)(this.Component,p,n,{client:this.getAttribute(&quot;client&quot;)}),this.removeAttribute(&quot;ssr&quot;),this.dispatchEvent(new CustomEvent(&quot;astro:hydrate&quot;))});d(this,&quot;unmount&quot;,()=&gt;{this.isConnected||this.dispatchEvent(new CustomEvent(&quot;astro:unmount&quot;))})}disconnectedCallback(){document.removeEventListener(&quot;astro:after-swap&quot;,this.unmount),document.addEventListener(&quot;astro:after-swap&quot;,this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute(&quot;await-children&quot;)||document.readyState===&quot;interactive&quot;||document.readyState===&quot;complete&quot;)this.childrenConnectedCallback();else{let e=()=&gt;{document.removeEventListener(&quot;DOMContentLoaded&quot;,e),c.disconnect(),this.childrenConnectedCallback()},c=new MutationObserver(()=&gt;{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&amp;&amp;this.lastChild.nodeValue===&quot;astro:end&quot;&amp;&amp;(this.lastChild.remove(),e())});c.observe(this,{childList:!0}),document.addEventListener(&quot;DOMContentLoaded&quot;,e)}}async childrenConnectedCallback(){let e=this.getAttribute(&quot;before-hydration-url&quot;);e&amp;&amp;await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute(&quot;opts&quot;)),c=this.getAttribute(&quot;client&quot;);if(Astro[c]===void 0){window.addEventListener(`astro:${c}`,()=&gt;this.start(),{once:!0});return}try{await Astro[c](async()=&gt;{let n=this.getAttribute(&quot;renderer-url&quot;),[h,{default:p}]=await Promise.all([import(this.getAttribute(&quot;component-url&quot;)),n?import(n):()=&gt;()=&gt;{}]),u=this.getAttribute(&quot;component-export&quot;)||&quot;default&quot;;if(!u.includes(&quot;.&quot;))this.Component=h[u];else{this.Component=h;for(let f of u.split(&quot;.&quot;))this.Component=this.Component[f]}return this.hydrator=p,this.hydrate},e,this)}catch(n){console.error(`[astro-island] Error hydrating ${this.getAttribute(&quot;component-url&quot;)}`,n)}}attributeChangedCallback(){this.hydrate()}}d(y,&quot;observedAttributes&quot;,[&quot;props&quot;]),customElements.get(&quot;astro-island&quot;)||customElements.define(&quot;astro-island&quot;,y)}})();&lt;/script&gt;&lt;astro-island uid=&quot;v0304&quot; prefix=&quot;r3&quot; component-url=&quot;/opt/build/repo/embeds/ClientEmbed.tsx&quot; component-export=&quot;ClientEmbed&quot; renderer-url=&quot;@astrojs/react/client.js&quot; props=&quot;{&amp;quot;providerId&amp;quot;:[0,&amp;quot;vimeo&amp;quot;],&amp;quot;wrapperClass&amp;quot;:[0,&amp;quot;aspect-video&amp;quot;],&amp;quot;vimeoId&amp;quot;:[0,&amp;quot;876040038&amp;quot;]}&quot; ssr=&quot;&quot; client=&quot;visible&quot; before-hydration-url=&quot;astro:scripts/before-hydration.js&quot; opts=&quot;{&amp;quot;name&amp;quot;:&amp;quot;ClientEmbed&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;rootMargin&amp;quot;:&amp;quot;400px&amp;quot;}}&quot; await-children=&quot;&quot;&gt;&lt;div class=&quot;aspect-video&quot;&gt;&lt;div data-testid=&quot;general-observer&quot; class=&quot;mdx-embed&quot;&gt;&lt;div style=&quot;height:0;width:100%&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--astro:end--&gt;&lt;/astro-island&gt; &lt;/div&gt;&lt;p&gt;I also found a couple of good examples from &lt;a href=&quot;https://twitter.com/jordibruin&quot;&gt;Jordi Bruin‘s&lt;/a&gt;&lt;a href=&quot;https://github.com/jordibruin/TipKit-Examples/&quot;&gt;TipKit-Examples&lt;/a&gt; repo, which is more up to date than Apple‘s own &lt;a href=&quot;https://developer.apple.com/documentation/tipkit/highlightingappfeatureswithtipkit&quot;&gt;sample project&lt;/a&gt;.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;If you need a team to help you implement TipKit in your app, &lt;a href=&quot;https://lickability.com/contact&quot;&gt;reach out to us&lt;/a&gt; — we’d love to talk.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Michael Amundsen</author></item><item><title>Choosing the Right Technology for Your Mobile App</title><link>https://lickability.com/blog/choosing-the-right-technology-for-your-mobile-app/</link><guid isPermaLink="true">https://lickability.com/blog/choosing-the-right-technology-for-your-mobile-app/</guid><description>Our upcoming webinar</description><pubDate>Mon, 16 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Have you heard? Next week, we’re doing our first webinar!&lt;/p&gt;&lt;p&gt;A few of our staff iOS engineers will be co-hosting the webinar to guide you through the landscape of app development frameworks and options. We’ll explore the distinctions between native and cross-platform development, delve into &amp;amp; compare and contrast key cross-platform technologies like Flutter and React Native, and provide examples to help you match your app project with the perfect technology.&lt;/p&gt;&lt;p&gt;If you haven’t signed up yet, you can check out the details and RSVP &lt;a href=&quot;https://lickab.ly/webinar&quot;&gt;&lt;strong&gt;here&lt;/strong&gt;&lt;/a&gt;. We can’t wait to see you!&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Conferences Condensed: NSSpain 2023</title><link>https://lickability.com/blog/conferences-condensed-nsspain-2023/</link><guid isPermaLink="true">https://lickability.com/blog/conferences-condensed-nsspain-2023/</guid><description>My first conference talk</description><pubDate>Wed, 11 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last year was my first time attending &lt;a href=&quot;https://2023.nsspain.com/&quot;&gt;NSSpain&lt;/a&gt; — I met so many amazing, uplifting people who were super encouraging, and that empowered me to submit my talk to be considered as a speaker this year. I was elated to be selected to speak at the conference, and excited to bring my unique insights to NSSpain.&lt;/p&gt;&lt;p&gt;Along with my own talk, there were several talks I really enjoyed this year. Here’s a look at a few of my favorites.&lt;/p&gt;&lt;h3&gt;Run LLMs and diffusion models on Mac &amp;amp; iPhone&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;http://twitter.com/pcuenq&quot;&gt;&lt;strong&gt;Pedro Cuenca&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;I haven’t played around with LLMs outside of using &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;whisper.cpp&lt;/code&gt; to build a quick app that records audio input and translates it to text, which was pretty cool. But one thing that I noticed was the size of the model file — I was curious about how I could reduce the size, so this talk piqued my interest.&lt;/p&gt;&lt;p&gt;Pedro was focused on getting started with using CoreML to add a large language model to your apps, and running diffusion models in apps. He also delved into the pain points of trying to convert &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Py​Torch&lt;/code&gt; models to CoreML models. He took us through his journey of debugging when the conversion process crashed and providing a fix for the issue. By investigating and implementing that fix, he made a huge community impact by further allowing others to convert their &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Py​Torch&lt;/code&gt; to a CoreML model.&lt;/p&gt;&lt;p&gt;I was also happy to see that Pedro created an app called Diffusers, which allows you to try creating your own AI-generated images from prompts. I’ve played around with Midjourney a bit, and enjoy passing in prompts to see what would come of it, so Pedro’s app was interesting to me.&lt;/p&gt;&lt;h3&gt;2 Hours, 2 Days, 2 Weeks&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;http://twitter.com/jordibruin&quot;&gt;&lt;strong&gt;Jordi Bruin&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;When starting a side project, I always spend so much time thinking about the designs of an app, or trying to make it feature complete, bug-free, and “perfect.” And I become so consumed by these processes that I never ship anything.&lt;/p&gt;&lt;p&gt;Jordi’s talk highlighted that iteration is also an option when developing apps. Starting with something small and imperfect can be powerful in getting something out — and then you can add more to it later.&lt;/p&gt;&lt;p&gt;Jordi has a “2 hours, 2 days, 2 weeks” rule. He explained that when he thinks of an idea, he asks himself if the main concept can be prototyped in 2 hours. If it can, he then spends 2 days getting it into a state where he can test it and distribute it to friends and family. After that, he gives himself 2 weeks to polish and ship the app to the App Store.&lt;/p&gt;&lt;p&gt;He also told us about how he built a team to help with scaling his most popular apps. This was important to him, because he wanted ensure his existing apps were getting the attention they deserved while still leaving time to solve new problems.&lt;/p&gt;&lt;p&gt;One of the main takeaways from this talk for me was that it’s important to lower your expectations when building side projects. It is okay not to have a perfect product before you launch.&lt;/p&gt;&lt;h3&gt;Building UI components with SwiftUI&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://mastodon.social/@kasperl&quot;&gt;&lt;strong&gt;Kasper Lahti&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;In this talk, Kasper showed the considerations he made when creating custom SwiftUI components — demonstrating swipe actions specifically. He discussed the fact that many components in SwiftUI are customizable, but swipe actions are not as flexible. For example, he has come across cases where the swipe action’s font is different from the font used in the rest of an app, making it look inconsistent.&lt;/p&gt;&lt;p&gt;Kasper’s talk touched on some of the pain points when building custom components, like maintaining large files, which can be tedious if something breaks in a new software release. Finally, he took us through the process of creating a swipe action. He showed some of the code considerations that went into creating his custom swipe action.&lt;/p&gt;&lt;p&gt;This talk taught me not to be afraid to dig into the documentation — you can build complex UIs with SwiftUI, you just need to explore.&lt;/p&gt;&lt;h3&gt;Demystifying App Intents: A beginner’s guide&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Ashli Rankin&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot;not-prose&quot;&gt; &lt;style&gt;astro-island,astro-slot,astro-static-slot{display:contents}&lt;/style&gt;&lt;script&gt;(()=&gt;{var l=(s,i,o)=&gt;{let r=async()=&gt;{await(await s())()},t=typeof i.value==&quot;object&quot;?i.value:void 0,c={rootMargin:t==null?void 0:t.rootMargin},n=new IntersectionObserver(e=&gt;{for(let a of e)if(a.isIntersecting){n.disconnect(),r();break}},c);for(let e of o.children)n.observe(e)};(self.Astro||(self.Astro={})).visible=l;window.dispatchEvent(new Event(&quot;astro:visible&quot;));})();;(()=&gt;{var A=Object.defineProperty;var g=(i,o,a)=&gt;o in i?A(i,o,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[o]=a;var d=(i,o,a)=&gt;g(i,typeof o!=&quot;symbol&quot;?o+&quot;&quot;:o,a);{let i={0:t=&gt;m(t),1:t=&gt;a(t),2:t=&gt;new RegExp(t),3:t=&gt;new Date(t),4:t=&gt;new Map(a(t)),5:t=&gt;new Set(a(t)),6:t=&gt;BigInt(t),7:t=&gt;new URL(t),8:t=&gt;new Uint8Array(t),9:t=&gt;new Uint16Array(t),10:t=&gt;new Uint32Array(t),11:t=&gt;1/0*t},o=t=&gt;{let[l,e]=t;return l in i?i[l](e):void 0},a=t=&gt;t.map(o),m=t=&gt;typeof t!=&quot;object&quot;||t===null?t:Object.fromEntries(Object.entries(t).map(([l,e])=&gt;[l,o(e)]));class y extends HTMLElement{constructor(){super(...arguments);d(this,&quot;Component&quot;);d(this,&quot;hydrator&quot;);d(this,&quot;hydrate&quot;,async()=&gt;{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest(&quot;astro-island[ssr]&quot;);if(e){e.addEventListener(&quot;astro:hydrate&quot;,this.hydrate,{once:!0});return}let c=this.querySelectorAll(&quot;astro-slot&quot;),n={},h=this.querySelectorAll(&quot;template[data-astro-template]&quot;);for(let r of h){let s=r.closest(this.tagName);s!=null&amp;&amp;s.isSameNode(this)&amp;&amp;(n[r.getAttribute(&quot;data-astro-template&quot;)||&quot;default&quot;]=r.innerHTML,r.remove())}for(let r of c){let s=r.closest(this.tagName);s!=null&amp;&amp;s.isSameNode(this)&amp;&amp;(n[r.getAttribute(&quot;name&quot;)||&quot;default&quot;]=r.innerHTML)}let p;try{p=this.hasAttribute(&quot;props&quot;)?m(JSON.parse(this.getAttribute(&quot;props&quot;))):{}}catch(r){let s=this.getAttribute(&quot;component-url&quot;)||&quot;&lt;unknown&gt;&quot;,v=this.getAttribute(&quot;component-export&quot;);throw v&amp;&amp;(s+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${s}`,this.getAttribute(&quot;props&quot;),r),r}let u;await this.hydrator(this)(this.Component,p,n,{client:this.getAttribute(&quot;client&quot;)}),this.removeAttribute(&quot;ssr&quot;),this.dispatchEvent(new CustomEvent(&quot;astro:hydrate&quot;))});d(this,&quot;unmount&quot;,()=&gt;{this.isConnected||this.dispatchEvent(new CustomEvent(&quot;astro:unmount&quot;))})}disconnectedCallback(){document.removeEventListener(&quot;astro:after-swap&quot;,this.unmount),document.addEventListener(&quot;astro:after-swap&quot;,this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute(&quot;await-children&quot;)||document.readyState===&quot;interactive&quot;||document.readyState===&quot;complete&quot;)this.childrenConnectedCallback();else{let e=()=&gt;{document.removeEventListener(&quot;DOMContentLoaded&quot;,e),c.disconnect(),this.childrenConnectedCallback()},c=new MutationObserver(()=&gt;{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&amp;&amp;this.lastChild.nodeValue===&quot;astro:end&quot;&amp;&amp;(this.lastChild.remove(),e())});c.observe(this,{childList:!0}),document.addEventListener(&quot;DOMContentLoaded&quot;,e)}}async childrenConnectedCallback(){let e=this.getAttribute(&quot;before-hydration-url&quot;);e&amp;&amp;await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute(&quot;opts&quot;)),c=this.getAttribute(&quot;client&quot;);if(Astro[c]===void 0){window.addEventListener(`astro:${c}`,()=&gt;this.start(),{once:!0});return}try{await Astro[c](async()=&gt;{let n=this.getAttribute(&quot;renderer-url&quot;),[h,{default:p}]=await Promise.all([import(this.getAttribute(&quot;component-url&quot;)),n?import(n):()=&gt;()=&gt;{}]),u=this.getAttribute(&quot;component-export&quot;)||&quot;default&quot;;if(!u.includes(&quot;.&quot;))this.Component=h[u];else{this.Component=h;for(let f of u.split(&quot;.&quot;))this.Component=this.Component[f]}return this.hydrator=p,this.hydrate},e,this)}catch(n){console.error(`[astro-island] Error hydrating ${this.getAttribute(&quot;component-url&quot;)}`,n)}}attributeChangedCallback(){this.hydrate()}}d(y,&quot;observedAttributes&quot;,[&quot;props&quot;]),customElements.get(&quot;astro-island&quot;)||customElements.define(&quot;astro-island&quot;,y)}})();&lt;/script&gt;&lt;astro-island uid=&quot;1jY0Rr&quot; prefix=&quot;r0&quot; component-url=&quot;/opt/build/repo/embeds/ClientEmbed.tsx&quot; component-export=&quot;ClientEmbed&quot; renderer-url=&quot;@astrojs/react/client.js&quot; props=&quot;{&amp;quot;providerId&amp;quot;:[0,&amp;quot;vimeo&amp;quot;],&amp;quot;wrapperClass&amp;quot;:[0,&amp;quot;aspect-video&amp;quot;],&amp;quot;vimeoId&amp;quot;:[0,&amp;quot;865874323&amp;quot;]}&quot; ssr=&quot;&quot; client=&quot;visible&quot; before-hydration-url=&quot;astro:scripts/before-hydration.js&quot; opts=&quot;{&amp;quot;name&amp;quot;:&amp;quot;ClientEmbed&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;rootMargin&amp;quot;:&amp;quot;400px&amp;quot;}}&quot; await-children=&quot;&quot;&gt;&lt;div class=&quot;aspect-video&quot;&gt;&lt;div data-testid=&quot;general-observer&quot; class=&quot;mdx-embed&quot;&gt;&lt;div style=&quot;height:0;width:100%&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--astro:end--&gt;&lt;/astro-island&gt; &lt;/div&gt;&lt;p&gt;My talk was about why having shortcuts is important for apps, and how I created one based on a problem I had — I spoke about my weight loss journey and using apps to track my progress. I also touched on how to go about creating a simple &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;App​Intent&lt;/code&gt;, and we went through the process of using SiriKit.&lt;/p&gt;&lt;p&gt;Then, I talked about why apps should have shortcuts by highlighting that if your app saves people time, it reduces distraction and is more accessible. Finally, we went over some ways to ensure users get the most out of the intents we built by making them discoverable and accessible through prompts — which is important, because anyone should be able to easily use our apps.&lt;/p&gt;&lt;p&gt;This was my first time speaking at a conference, and the whole experience has been extremely liberating for me. I’m so glad I got the opportunity to attend NSSpain again — looking forward to the next one!&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Wanna hear more from our engineers? Sign up for our upcoming &lt;a href=&quot;/blog/choosing-the-right-technology-for-your-mobile-app&quot; title=&quot;Choosing the Right Technology for Your Mobile App&quot; data-astro-cid-okzjwv6e&gt;webinar&lt;/a&gt;  on choosing the right technology for your app.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Ashli Rankin</author></item><item><title>What We Love About Android Development</title><link>https://lickability.com/blog/what-we-love-about-android-development/</link><guid isPermaLink="true">https://lickability.com/blog/what-we-love-about-android-development/</guid><description>Wait, did you say Android??</description><pubDate>Thu, 07 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At Lickability, we’re best known for crafting beautiful iOS and Mac apps. Many of our employees have been working on Apple platforms long enough to remember the transition to &lt;a href=&quot;https://en.wikipedia.org/wiki/Objective-C#Objective-C_2.0&quot;&gt;Objective-C 2.0&lt;/a&gt;. Or the transition from &lt;a href=&quot;https://en.wikipedia.org/wiki/Reference_counting&quot;&gt;manual reference counting&lt;/a&gt; to &lt;a href=&quot;https://developer.apple.com/documentation/foundation/object_runtime/objective-c_garbage_collection?language=objc&quot;&gt;garbage collection&lt;/a&gt; (remember??) to &lt;a href=&quot;https://clang.llvm.org/docs/AutomaticReferenceCounting.html&quot;&gt;ARC&lt;/a&gt;. From Objective-C to Swift, from Swift 2 to 3 (that one took us a while 🥵)… you get the picture. In the early days of Lickability, we often needed to turn down work from clients looking to simultaneously build out both iOS and Android apps due to subcontractor availability and the gaps in our own knowledge on platforms other than Apple’s.&lt;/p&gt;&lt;p&gt;Years ago, we decided to address these gaps. If you’re an avid reader of our blog, you may associate Lickability solely with Apple, but we also love Android development. Over multiple internal and client projects, our team has built up a wealth of native Android and Flutter development knowledge, and we’d like to start sharing our experiences on our blog to gauge interest in future musings about how things work on “the other side.”&lt;/p&gt;&lt;p&gt;To kick things off, here are some of our favorite aspects of Android development that Apple platform developers might find interesting in contrast.&lt;/p&gt;&lt;h3&gt;Architecture&lt;/h3&gt;&lt;p&gt;Which primary architecture or design pattern is your go-to for Mac and iOS projects? If you like to keep things simple, maybe you’ve stuck with &lt;a href=&quot;https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/Model-View-Controller/Model-View-Controller.html&quot;&gt;MVC&lt;/a&gt; throughout the years. Or maybe &lt;a href=&quot;https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel&quot;&gt;MVVM&lt;/a&gt; strikes your fancy. &lt;a href=&quot;https://www.objc.io/issues/13-architecture/viper/&quot;&gt;VIPER&lt;/a&gt;? Are you working in SwiftUI? Do you use &lt;a href=&quot;https://github.com/pointfreeco/swift-composable-architecture&quot;&gt;The Composable Architecture&lt;/a&gt;? (We do, and we love it.)&lt;/p&gt;&lt;p&gt;Things work a bit differently on Android, as these decisions come from the top. Rather than providing the platform APIs and leaving it up to the community to determine which architecture pattern is best, Android provides &lt;a href=&quot;https://developer.android.com/jetpack&quot;&gt;Jetpack&lt;/a&gt; libraries (in short, libraries that help you to write better, modern code, following suggested architecture best practices), and an absurdly good and detailed &lt;a href=&quot;https://developer.android.com/topic/architecture&quot;&gt;architecture guide&lt;/a&gt;. Take a moment to skim through that guide, and some of the pages that it links to. Does your neck hurt from aggressively nodding along to headings like “Single source of truth” or “Unidirectional Data Flow”? Ours does. Yes, we collectively have one neck.&lt;/p&gt;&lt;p&gt;This architecture guide, and the libraries that support it, take away a lot of the guesswork for newcomers and experienced developers alike. When spinning up a new iOS project, we find ourselves missing this level of direction from Apple. And while you’re still free to use any patterns you wish, sticking with the best practices, and never being confused about what &lt;em&gt;is&lt;/em&gt; the best practice, will get you very far, very quickly.&lt;/p&gt;&lt;p&gt;As an aside, we love that &lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/viewmodel&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;View​Model&lt;/code&gt;&lt;/a&gt; is a Jetpack-provided type, and its importance in both separation of concerns and its crucial role in the Android lifecycle. You won’t find us &lt;a href=&quot;https://lickability.com/blog/our-view-on-view-models/&quot;&gt;arguing about its definition&lt;/a&gt; while working on this platform.&lt;/p&gt;&lt;h3&gt;Enforced Conventions&lt;/h3&gt;&lt;p&gt;On Apple platforms, we’ve established good organizational conventions like using asset catalogs for colors and images and organizing our localizable strings in a sensible manner. However, much of this isn’t strictly enforced, and it‘s up to us to document project or team-specific best practices, or enforce them with additional tools and linters. On Android, the IDE has your back, from creating a sensible project folder structure, to cleaning up individual lines of code.&lt;/p&gt;&lt;p&gt;Suppose a developer hardcodes a string in a layout. That’s a simple warning with an auto-fix to move it to the created-by-default &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;strings.xml&lt;/code&gt; resource file. Android Studio does everything it can to help you in situations like this.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Android Studio screenshot showing the right-click menu of a warning stating that a button is using hardcoded text. The highlighted auto-fix option in the menu is to extract the string resource.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/711d7f890c7c489b2a52598462f433c1ec044246-2314x1202.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/711d7f890c7c489b2a52598462f433c1ec044246-2314x1202.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/711d7f890c7c489b2a52598462f433c1ec044246-2314x1202.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/711d7f890c7c489b2a52598462f433c1ec044246-2314x1202.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/711d7f890c7c489b2a52598462f433c1ec044246-2314x1202.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/711d7f890c7c489b2a52598462f433c1ec044246-2314x1202.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2314 2314w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/711d7f890c7c489b2a52598462f433c1ec044246-2314x1202.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;831&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Are you not following accessibility best practices like properly describing images or using too-low-contrast text? The IDE knows, and will tell you.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Android Studio screenshot showing the right-click menu of a warning stating that an image view&amp;#x27;s image is lacking a content description. The highlighted auto-fix option in the menu is to set a content description.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/2c18afad3bb3ca8319c64366e45eee09c1414a53-2314x1204.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/2c18afad3bb3ca8319c64366e45eee09c1414a53-2314x1204.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/2c18afad3bb3ca8319c64366e45eee09c1414a53-2314x1204.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/2c18afad3bb3ca8319c64366e45eee09c1414a53-2314x1204.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/2c18afad3bb3ca8319c64366e45eee09c1414a53-2314x1204.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/2c18afad3bb3ca8319c64366e45eee09c1414a53-2314x1204.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2314 2314w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/2c18afad3bb3ca8319c64366e45eee09c1414a53-2314x1204.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;832&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;You’ll even get help with basic syntax conventions without the need to configure any 3rd-party linters.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Android Studio screenshot showing a yellow underlined snippet of code that contains a lambda argument within a function call&amp;#x27;s parentheses. The popover tip suggests that the lambda argument should be moved out of the parentheses, and offers to do so via a keyboard shortcut.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/02794ee2d9e7b7f62873fbfe285f3895d31de18a-1156x414.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=289 289w, https://cdn.sanity.io/images/nkt6o869/production/02794ee2d9e7b7f62873fbfe285f3895d31de18a-1156x414.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=578 578w, https://cdn.sanity.io/images/nkt6o869/production/02794ee2d9e7b7f62873fbfe285f3895d31de18a-1156x414.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=867 867w, https://cdn.sanity.io/images/nkt6o869/production/02794ee2d9e7b7f62873fbfe285f3895d31de18a-1156x414.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1156 1156w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/02794ee2d9e7b7f62873fbfe285f3895d31de18a-1156x414.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1156&quot; width=&quot;1156&quot; height=&quot;414&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;And these are just a few small examples. We comfortably rely on the IDE for this type of help throughout the day. Newcomers to our Android projects find it extremely valuable for staying productive in an unfamiliar environment, and it helps the rest of us when reviewing code, knowing that the tools have given the developer a leg up, allowing us to focus more on logic than proper adherence to conventions.&lt;/p&gt;&lt;h3&gt;Hands-on Tutorials&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Screenshot of the &amp;quot;Android Basics in Kotlin&amp;quot; course page showing several course units, with more cropped at the bottom, intended to show how comprehensive the course is.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/1d736b6f11f641e77f2993a619a43baab62fa2b9-2404x2308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/1d736b6f11f641e77f2993a619a43baab62fa2b9-2404x2308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/1d736b6f11f641e77f2993a619a43baab62fa2b9-2404x2308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/1d736b6f11f641e77f2993a619a43baab62fa2b9-2404x2308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/1d736b6f11f641e77f2993a619a43baab62fa2b9-2404x2308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/1d736b6f11f641e77f2993a619a43baab62fa2b9-2404x2308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/1d736b6f11f641e77f2993a619a43baab62fa2b9-2404x2308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2404 2404w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/1d736b6f11f641e77f2993a619a43baab62fa2b9-2404x2308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1536&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Apple has been doing great work lately with their series of interactive tutorials. Take a brief look through the SwiftUI &lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/creating-and-combining-views&quot;&gt;Creating and Combining Views&lt;/a&gt; tutorial. It’s pretty incredible. I wish we had this level of interactive tutorials when we were just starting out! It’s hard to argue that individual first-party Android tutorials are &lt;em&gt;better&lt;/em&gt; or &lt;em&gt;more polished&lt;/em&gt; than these, but they definitely beat out Apple in terms of comprehensiveness.&lt;/p&gt;&lt;p&gt;Android codelabs contain detailed walkthroughs on just about any topic you might need help with when starting your Android career, or familiarizing yourself with something new. Take for example the &lt;a href=&quot;https://developer.android.com/courses/android-basics-kotlin/course?hl=en&quot;&gt;Android Basics in Kotlin&lt;/a&gt; course pictured above. This course is divided into 6 high-level &lt;em&gt;units&lt;/em&gt;, each comprised of multiple &lt;a href=&quot;https://developer.android.com/courses/android-basics-kotlin/unit-2?hl=en&quot;&gt;&lt;em&gt;pathways&lt;/em&gt;&lt;/a&gt; that consist of multiple &lt;a href=&quot;https://developer.android.com/courses/pathways/android-basics-kotlin-unit-2-pathway-2?hl=en&quot;&gt;videos, codelabs, and quizzes&lt;/a&gt;. Each codelab walks you through a specific concept in great detail as you work through sample projects. Concepts are explained very clearly, and if you’re a hands-on learner, you’re in luck, because you’ll be writing a lot of code.&lt;/p&gt;&lt;p&gt;So if you’re &lt;a href=&quot;https://developer.android.com/codelabs/basic-android-kotlin-compose-first-app?hl=en&quot;&gt;just starting out&lt;/a&gt;, or you need a refresher on a &lt;a href=&quot;https://developer.android.com/courses/pathways/android-coroutines?hl=en&quot;&gt;more advanced topic&lt;/a&gt;, codelabs have you covered.&lt;/p&gt;&lt;h3&gt;Editor Speed&lt;/h3&gt;&lt;p&gt;I’ve said some nice things about features in the IDE, but ultimately it’s a non-native looking and feeling application, and I’d be lying if I said jumping between Xcode and Android Studio makes for smooth transitions. I’m often tripped up while navigating between files, or frustrated when I expect a platform standard to apply, like option-clicking a folder in an outline view to expand all subfolders. I prefer native, and I prefer Xcode, but that doesn’t mean it’s purely a slog to edit code in Android Studio.&lt;/p&gt;&lt;p&gt;In fact, I find the actual code editing experience extremely nice, which you’ll spend a lot more time on than fiddling with vertical tabs, maximum heap size, and other unfamiliar frustrations.&lt;/p&gt;&lt;p&gt;The speed of auto-completion and auto-importing is impressive.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A gif showing the speed of auto-complete suggestions in Android Studio when specifying a subclass, and subsequently, the auto-import that appears upon accepting the suggestion.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9ed353ae34e20548b5dde3d5cb0fe5b235321091-800x306.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/9ed353ae34e20548b5dde3d5cb0fe5b235321091-800x306.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/9ed353ae34e20548b5dde3d5cb0fe5b235321091-800x306.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/9ed353ae34e20548b5dde3d5cb0fe5b235321091-800x306.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9ed353ae34e20548b5dde3d5cb0fe5b235321091-800x306.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800&quot; width=&quot;800&quot; height=&quot;306&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Technically, Xcode supports auto-importing, but in practice, I’ve only seen it work sporadically (I should file feedback…). Especially as the project size grows, I find myself waiting on Xcode to catch up to what I’m thinking. When writing Kotlin, auto-complete suggestions feel instantaneous. The editor waits on me. Android developers aren’t necessarily spoiled here, as many IDEs are this quick. We’re just used to one that takes a while.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;So what do you think? Have we &lt;a href=&quot;https://en.wikipedia.org/wiki/Jumping_the_shark&quot;&gt;jumped the shark&lt;/a&gt; here on our blog by discussing a competing platform, or would like to hear from us more about our dabbling in non-Apple technologies? &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;Let us know&lt;/a&gt; &lt;a href=&quot;https://mastodon.social/@lickability&quot;&gt;what you think&lt;/a&gt;. And of course, if you or your company need help building an Android app, &lt;a href=&quot;https://lickability.com/contact&quot;&gt;reach out&lt;/a&gt;, because we do that too!&lt;/p&gt; </content:encoded><author>Michael Liberatore</author></item><item><title>Plussing your iOS app</title><link>https://lickability.com/blog/plussing-your-ios-app/</link><guid isPermaLink="true">https://lickability.com/blog/plussing-your-ios-app/</guid><description>Easy drop-ins for a better user experience</description><pubDate>Mon, 28 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SwiftUI is really good. (Stop booing me, I’m right.) However, there comes a time when you may look at your app and think, “this reeks of SwiftUI.” System-provided list layouts, the same typography, the same colors as every other app.&lt;/p&gt;&lt;p&gt;Listen, those are fine defaults. It’s why those design choices are defaults — because they look fine and your users already understand them. But there comes a time when you want to go the extra mile. Friend of The Company™ &lt;a href=&quot;https://www.swiftjectivec.com/about&quot;&gt;Jordan Morgan&lt;/a&gt; calls the apps that do this &lt;a href=&quot;https://www.swiftjectivec.com/a-best-in-class-app/&quot;&gt;“best in class.”&lt;/a&gt; I call it &lt;a href=&quot;https://www.waltdisney.org/blog/walts-own-words-plussing-disneyland&quot;&gt;&lt;em&gt;plussing&lt;/em&gt;&lt;/a&gt;: &lt;strong&gt;taking a perfectly fine starting point and extending it with some easy wins&lt;/strong&gt;. It’s your app, plus a little extra spice. It’s like having a coffee and then adding a touch of your favorite flavoring or syrup. It‘s the same base drink, but with a little something extra to make it special.&lt;/p&gt;&lt;p&gt;Plussing is a broad term and how you achieve it is ultimately up to you. My background is in design, so we’ll explore spacing, typography, color, and some lesser known features of system controls you can use to enhance your app in small but meaningful ways.&lt;/p&gt;&lt;p&gt;You can use all of these tips without any additional libraries. Just you, your Xcode project, and the open road. Let’s begin.&lt;/p&gt;&lt;h3&gt;🔭 Spacing Systems? Far Out.&lt;/h3&gt;&lt;p&gt;Design systems are so, so easy to use on the web. That’s &lt;a href=&quot;https://storybook.js.org/&quot;&gt;Storybook&lt;/a&gt;’s whole thing. On native platforms — iOS especially — it’s more difficult to customize standard views. A slider, for instance, only lets you change the track color. But there are some things you can do to make your design more cohesive by employing a global set of values.&lt;/p&gt;&lt;p&gt;At Lickability, we typically use an exponential grid for spacing and sizing, which helps us make sure our layout remains balanced.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;enum&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Grid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; extraExtraSmall: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; extraSmall: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; small: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; medium: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; large: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; extraLarge: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;64&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; CircleButton&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;View &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; body: &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; View {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        Button&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;action&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: { }) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;            Image&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;systemName&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;plus&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;font&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;system&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: Grid.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;large&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;foregroundColor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;white&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;frame&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: Grid.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;extraLarge&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: Grid.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;extraLarge&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;background&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(Color.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;indigo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;gradient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;clipShape&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Circle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;shadow&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;color&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: Color.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;black&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;opacity&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;radius&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: Grid.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;small&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;y&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: Grid.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;small&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A plus button. Circular, with a drop shadow, and an indigo gradient.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b0de7520945e0737dc4d98fc3dcc20d7840cd073-524x472.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=262 262w, https://cdn.sanity.io/images/nkt6o869/production/b0de7520945e0737dc4d98fc3dcc20d7840cd073-524x472.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=524 524w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b0de7520945e0737dc4d98fc3dcc20d7840cd073-524x472.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=524&quot; width=&quot;524&quot; height=&quot;472&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Lovely.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Base 2 exponential values are nice because it makes mental math easy when I’m designing, but you can (should?) experiment with different values. I replaced the grid with a &lt;a href=&quot;https://www.modularscale.com/&quot;&gt;modular scale&lt;/a&gt; with base 2, ratio: 3 (2, 6, 18, 54, 162) and got a dramatically different visual rhythm:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;The same indigo plus button, but with far more padding around the plus icon.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/5486ef0643bb2be7e41e02006b74c9e2ea500a45-489x391.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=245 245w, https://cdn.sanity.io/images/nkt6o869/production/5486ef0643bb2be7e41e02006b74c9e2ea500a45-489x391.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=489 489w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/5486ef0643bb2be7e41e02006b74c9e2ea500a45-489x391.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=489&quot; width=&quot;489&quot; height=&quot;391&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;More spacious than any apartment I can afford.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;🎨 Elevating Aesthetics with Color Grading&lt;/h3&gt;&lt;p&gt;When I was a kid, I wanted to get into cinema post-production. Now, I draw rectangles and write blog posts about drawing rectangles. Things have not gone to plan. But my brief stint in cinematography made me see the value in using subtle color adjustments to set the tone of the scene — or, in this case, create a more harmonious interface.&lt;/p&gt;&lt;p&gt;By default, the system-provided neutral colors (like &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;secondary​System​Fill&lt;/code&gt;) carry a blue tint. But if your app’s accent color is, say, pink or green, you still get slightly blue background colors, secondary labels, and shape fills. The difference some manual color adjustments can make is lovely.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A mockup of three iOS screenshots with pink, green, and blue accents. The labels, buttons, backgrounds are all tinted respectively. The normally-pure-black title in the green mockup, for example, now takes on a deep forest green color.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/db2733f9b0b78f837c894e3f5ce5eefdc33d857a-2012x1058.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/db2733f9b0b78f837c894e3f5ce5eefdc33d857a-2012x1058.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/db2733f9b0b78f837c894e3f5ce5eefdc33d857a-2012x1058.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/db2733f9b0b78f837c894e3f5ce5eefdc33d857a-2012x1058.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/db2733f9b0b78f837c894e3f5ce5eefdc33d857a-2012x1058.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/db2733f9b0b78f837c894e3f5ce5eefdc33d857a-2012x1058.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2012 2012w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/db2733f9b0b78f837c894e3f5ce5eefdc33d857a-2012x1058.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;841&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;For extra credit, you can replace the primary label colors (black or white) with off-black and off-white values. I generally avoid using pure black in designs since &lt;a href=&quot;https://ianstormtaylor.com/design-tip-never-use-black/&quot;&gt;it overpowers everything else&lt;/a&gt;, it limits what you can do with depth (you can’t put a shadow on pure black, for example), and &lt;a href=&quot;https://jessicaotis.com/academia/never-use-white-text-on-a-black-background-astygmatism-and-conference-slides/&quot;&gt;reducing the contrast between pure black and pure white helps mitigate halation&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The battery savings touted by “OLED black” is greatly overstated — the difference it makes is &lt;a href=&quot;https://www.xda-developers.com/amoled-black-vs-gray-dark-mode/&quot;&gt;a fraction of a percent&lt;/a&gt; per hour.&lt;/p&gt;&lt;h3&gt;🔠 Typography Tweaks for Fun and Profit (and Security)&lt;/h3&gt;&lt;p&gt;When creating views with usernames or email addresses — contexts that are susceptible to &lt;a href=&quot;https://www.malwarebytes.com/blog/news/2017/10/out-of-character-homograph-attacks-explained&quot;&gt;homoglyph attacks&lt;/a&gt; — I’ve started employing this trick to make some of the similar-looking characters more distinct.&lt;/p&gt;&lt;p&gt;The system font has a stylistic alternative set of characters under &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;k​Stylistic​Alt​Six​On​Selector&lt;/code&gt; for this exact purpose. Take a look at the sample text here and notice the uppercase i, lowercase L, and the digits.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Two blocks of the same text. One block uses the default iOS system font, the other uses the system font with some modified letterforms. The uppercase I has horizontal serifs, the lowercase L takes has a bit of a curved tail, zeros are slashed, and the counters on 4, 6, and 9 are wider.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9bf8bc738b40cf35705b40124a34774625a2204f-656x700.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=328 328w, https://cdn.sanity.io/images/nkt6o869/production/9bf8bc738b40cf35705b40124a34774625a2204f-656x700.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=656 656w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9bf8bc738b40cf35705b40124a34774625a2204f-656x700.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=656&quot; width=&quot;656&quot; height=&quot;700&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;It’s not easy to use this (luckily I found &lt;a href=&quot;https://github.com/anonymouscamera/anonymous-camera/blob/b7ae19b07a476c0a8a54274fdaadd9e5ecd811d5/anonymous-camera/Helpers/Helpers.swift&quot;&gt;this helper function&lt;/a&gt;), but I got a blessing from an Apple engineer in a WWDC lab that doing this is kosher:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;extension&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; UIFont&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; fontWithFeature&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) -&gt; UIFont {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; originalDesc = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;fontDescriptor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; features:[UIFontDescriptor.AttributeName: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Any&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;] = [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            UIFontDescriptor.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;AttributeName&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;featureSettings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    UIFontDescriptor.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;FeatureKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;featureIdentifier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: key,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    UIFontDescriptor.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;FeatureKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;typeIdentifier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: value&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; newDesc = originalDesc.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;addingAttributes&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(features)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; UIFont&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;descriptor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: newDesc, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; fontWithHighLegibility&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() -&gt; UIFont {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fontWithFeature&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: kStylisticAlternativesType, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: kStylisticAltSixOnSelector)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ContentView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;View &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; body: &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; View {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;font&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Font&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(UIFont&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;preferredFont&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forTextStyle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;title2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fontWithHighLegibility&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            ))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;On the topic of typography, the system provides &lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/typography&quot;&gt;a number of different font styles&lt;/a&gt;, all of which afford you the nice things that the standard system font does: line heights that fit in nicely with SF Symbols, proper Dynamic Type adjustments for free, variable weights, and support for the Bold Text accessibility setting.&lt;/p&gt;&lt;p&gt;I’m a fan of New York (serif) for a classier look. Take a look at the &lt;a href=&quot;https://www.apple.com/apple-books/&quot;&gt;Books app&lt;/a&gt; for how Apple balances serif titles and sans serif body text.&lt;/p&gt;&lt;h3&gt;🫥 Empathy in Design: Handling Empty States&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Two iPhones. One iPhone shows a navigation bar titled &amp;quot;Favorites&amp;quot; and then a blank screen. The other has the navigation title and a block of text centered in the middle of the screen: &amp;quot;No favorites yet. Any items you favorite will show up here.&amp;quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/1b4b0a8ee309377c89f9fecec9a5e3b733c8ec13-2560x1600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/1b4b0a8ee309377c89f9fecec9a5e3b733c8ec13-2560x1600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/1b4b0a8ee309377c89f9fecec9a5e3b733c8ec13-2560x1600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/1b4b0a8ee309377c89f9fecec9a5e3b733c8ec13-2560x1600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/1b4b0a8ee309377c89f9fecec9a5e3b733c8ec13-2560x1600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/1b4b0a8ee309377c89f9fecec9a5e3b733c8ec13-2560x1600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/1b4b0a8ee309377c89f9fecec9a5e3b733c8ec13-2560x1600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2560 2560w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/1b4b0a8ee309377c89f9fecec9a5e3b733c8ec13-2560x1600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1000&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Quick, which app has finished loading content?&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;I wanted to title this section “Empt-athy in design” but it didn’t read well.&lt;/p&gt;&lt;p&gt;Honestly, this is my biggest pet peeve: when I don’t have any content in a section of an app and, instead of the app telling me that I don’t have any content, I find myself staring at a blank screen with no indication that it’s no longer loading.&lt;/p&gt;&lt;p&gt;If you’re targeting iOS 17 or later, this is drop-dead easy — just use &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/contentunavailableview&quot;&gt;ContentUnavailableView&lt;/a&gt;. Otherwise, I’m begging you to just slot in an empty state view. Nothing fancy, just a big SF Symbol, a headline, and some body text. Maybe a plain button or two.&lt;/p&gt;&lt;h3&gt;≡ Menus are Really Good, Actually&lt;/h3&gt;&lt;p&gt;Where it makes sense, you can convert buttons to &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/view/contextmenu(forselectiontype:menu:primaryaction:)&quot;&gt;menus with a primary action&lt;/a&gt;. These menus work by performing the primary action on tap and opening the context menu on long press. And it’s all provided by the system!&lt;/p&gt;&lt;p&gt;If you’ve got a button in your navigation bar for “New Task,” you can use a menu there. The primary action would create a default task, and you can put the more niche items under the menu (”New Location-Based Task” or “New Date-Based Task”). Of course, use your best judgement here — but it’s a great little addition that not a ton of people know about.&lt;/p&gt;&lt;p&gt;When creating the actual menu, options should be written in a way that don’t require explanation beyond the menu label itself. But if you need to provide additional information on a menu item, you can! It’s hidden, but one of our engineers &lt;a href=&quot;https://twitter.com/lickability/status/1648400623262662658&quot;&gt;discovered that you can add a second text view&lt;/a&gt; in a button’s label parameter.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A SwiftUI view running in the Swift Playgrounds iPad app. The view contains a menu with three buttons. The label for each button contains two unsettled Text views. On the right, a preview shows the open menu. The first text view is presented as the menu option title, the second is presented as a smaller, gray subtitle.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/ba5ad4bbb903d17f86ec34383fc41f8f118de48a-2388x1668.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/ba5ad4bbb903d17f86ec34383fc41f8f118de48a-2388x1668.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/ba5ad4bbb903d17f86ec34383fc41f8f118de48a-2388x1668.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/ba5ad4bbb903d17f86ec34383fc41f8f118de48a-2388x1668.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/ba5ad4bbb903d17f86ec34383fc41f8f118de48a-2388x1668.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/ba5ad4bbb903d17f86ec34383fc41f8f118de48a-2388x1668.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2388 2388w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/ba5ad4bbb903d17f86ec34383fc41f8f118de48a-2388x1668.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1118&quot;/&gt;  &lt;/figure&gt; &lt;h2&gt;🔍 Details Matter and Matters Detail&lt;/h2&gt;&lt;p&gt;Those are just a few ways that came to mind when plussing your app from a visual design and user experience standpoint. But I recognize most of you are developers, and there are so many more ways to plus beyond design. You can plus your app further by &lt;a href=&quot;https://developer.apple.com/documentation/metrickit/improving_your_app_s_performance&quot;&gt;optimizing performance&lt;/a&gt;, integrating more system accessibility features like the &lt;a href=&quot;https://swiftwithmajid.com/2021/09/14/accessibility-rotors-in-swiftui/&quot;&gt;rotor&lt;/a&gt; and &lt;a href=&quot;https://lickability.com/blog/dynamic-type-and-in-app-font-scaling/&quot;&gt;dynamic type&lt;/a&gt;, and auditing text fields to add &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uitextcontenttype&quot;&gt;content hints&lt;/a&gt; so autofill works correctly. While you’re at it, check if you’re using the right &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uikeyboardtype&quot;&gt;keyboard type&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;While SwiftUI provides a solid foundation, it‘s up to you to give your app that extra sparkle. Your users will appreciate the unique design elements, making your app feel like a premium experience rather than just another SwiftUI app. After all, it’s the small things that count in creating a standout app. So go ahead, start plussing! If you need me, I’ll be here figuring out a better header for the empty state section.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Want expert help plussing your own app? That’s our specialty. &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Let’s talk&lt;/a&gt;!&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Sam Henri Gold</author></item><item><title>Introducing ObservableConverter</title><link>https://lickability.com/blog/introducing-observableconverter/</link><guid isPermaLink="true">https://lickability.com/blog/introducing-observableconverter/</guid><description>A plugin to help convert to @Observable</description><pubDate>Tue, 22 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At WWDC 2023, Apple &lt;a href=&quot;https://developer.apple.com/wwdc23/10149&quot;&gt;introduced&lt;/a&gt; the brand new &lt;a href=&quot;https://developer.apple.com/documentation/observation&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;@Observable&lt;/code&gt;&lt;/a&gt; macro, a simple way to define a type with properties that will automatically update SwiftUI views when they change, replacing the &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Observable​Object&lt;/code&gt; protocol and &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;@Published&lt;/code&gt; properties. This new feature is great, but what should we do with all of our old types that use &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;Observable​Object&lt;/code&gt; and outdated property wrappers like &lt;code index=&quot;11&quot; isInline=&quot;true&quot;&gt;@State​Object&lt;/code&gt;, &lt;code index=&quot;13&quot; isInline=&quot;true&quot;&gt;@Observed​Object&lt;/code&gt;, and &lt;code index=&quot;15&quot; isInline=&quot;true&quot;&gt;@Environment​Object&lt;/code&gt;? To kickstart this conversion in Xcode, we at Lickability started writing a small tool and are releasing the first beta today: &lt;a href=&quot;https://github.com/Lickability/ObservableConverter&quot;&gt;&lt;strong&gt;ObservableConverter&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Logo lockup: ObservableConverter by Lickability &quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/aa72ad54f16204a88b09005b28330a9686344a6a-2300x355.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;rect=0,0,2300,355&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/aa72ad54f16204a88b09005b28330a9686344a6a-2300x355.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;rect=0,0,2300,355&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/aa72ad54f16204a88b09005b28330a9686344a6a-2300x355.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;rect=0,0,2300,355&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/aa72ad54f16204a88b09005b28330a9686344a6a-2300x355.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;rect=0,0,2300,355&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/aa72ad54f16204a88b09005b28330a9686344a6a-2300x355.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;rect=0,0,2300,355&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/aa72ad54f16204a88b09005b28330a9686344a6a-2300x355.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;rect=0,0,2300,355&amp;amp;w=2300 2300w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/aa72ad54f16204a88b09005b28330a9686344a6a-2300x355.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;rect=0,0,2300,355&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;247&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;&lt;a href=&quot;https://github.com/Lickability/ObservableConverter&quot;&gt;ObservableConverter&lt;/a&gt; is a Swift command plugin that, when installed from Xcode 15, makes converting from &lt;code index=&quot;2&quot; isInline=&quot;true&quot;&gt;Observable​Object&lt;/code&gt; to using &lt;code index=&quot;4&quot; isInline=&quot;true&quot;&gt;@Observable&lt;/code&gt; literally just a few clicks after adding the dependency. Select your target in the project navigator, right click, and select &lt;strong&gt;Convert Target to Use @Observable&lt;/strong&gt;. It will update your &lt;code index=&quot;8&quot; isInline=&quot;true&quot;&gt;Observable​Object&lt;/code&gt;classes, convert all the old property wrappers, and make some view modifier updates. From the plugin’s executable, we make use of Apple’s &lt;code index=&quot;10&quot; isInline=&quot;true&quot;&gt;Swift​Syntax&lt;/code&gt; to get a view of the syntax tree for each file and make some safe replacements and modifications. Once you run the conversion, you can simply remove the dependency entirely!&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f7838606aea062caa3d90e6151e5b03df04b86d6-858x1388.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=215 215w, https://cdn.sanity.io/images/nkt6o869/production/f7838606aea062caa3d90e6151e5b03df04b86d6-858x1388.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=429 429w, https://cdn.sanity.io/images/nkt6o869/production/f7838606aea062caa3d90e6151e5b03df04b86d6-858x1388.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=644 644w, https://cdn.sanity.io/images/nkt6o869/production/f7838606aea062caa3d90e6151e5b03df04b86d6-858x1388.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=858 858w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f7838606aea062caa3d90e6151e5b03df04b86d6-858x1388.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=858&quot; width=&quot;858&quot; height=&quot;1388&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;The tool is still very much in beta, so while it covers converting from the use cases Apple covered in &lt;a href=&quot;https://developer.apple.com/wwdc23/10149?time=625&quot;&gt;&lt;strong&gt;Discover Observation in SwiftUI&lt;/strong&gt;&lt;/a&gt;, there are many more nuanced and &lt;a href=&quot;https://x.com/dsteppenbeck/status/1693314873382629643?s=46&amp;t=tovYta5yAiEgXU71cjbcyQ&quot;&gt;advanced cases&lt;/a&gt; that will require making some decisions on what’s best for your code. We plan to support some of those cases in the future, but for now we hope this is a nice place to start when adopting the new &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;@Observable&lt;/code&gt; macro! Feel free to file &lt;a href=&quot;https://github.com/Lickability/ObservableConverter/issues/new/choose&quot;&gt;feature requests or bugs&lt;/a&gt;.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;If you’d like our team to get your apps ready for iOS 17 (or macOS 14, tvOS 17, or watchOS 10) please &lt;a href=&quot;https://lickability.com/contact&quot;&gt;reach out&lt;/a&gt;!&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Brian Capps</author></item><item><title>In-App Purchases with RevenueCat on visionOS</title><link>https://lickability.com/blog/in-app-purchases-with-revenuecat-on-visionos/</link><guid isPermaLink="true">https://lickability.com/blog/in-app-purchases-with-revenuecat-on-visionos/</guid><description>A step-by-step guide</description><pubDate>Mon, 14 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With Apple introducing Apple Vision Pro at WWDC 2023, we’ve been incredibly excited to start building apps in a brand new paradigm: spatial computing. While we’ve been having fun putting the simulator through its paces &lt;a href=&quot;https://twitter.com/lickability/status/1683564649239674880&quot;&gt;building visionOS apps&lt;/a&gt;, we’ve also been thinking about how to monetize our apps to make them sustainable in the future. However, while there are plenty of available resources on RealityKit, Reality Composer Pro, and many of the technical aspects of building a visionOS app for Vision Pro, there are few on how purchases work or what they even look like!&lt;/p&gt;&lt;p&gt;When it comes to offering in-app purchases, our team &lt;a href=&quot;https://lickability.com/blog/revenuecat/&quot;&gt;loves using RevenueCat&lt;/a&gt;, which is the best platform for purchases and subscriptions across mobile and the web. They integrate well with Apple platforms, with native SDKs for each platform, and visionOS is no exception. For this process, we’ll be setting up a super simple in-app purchase on RevenueCat, integrating their SDK, and showing the product to the user for purchase on the visionOS simulator. If you’ve already got your app set up on RevenueCat, feel free to skip down to the integration. Now, let’s immerse ourselves. 🥽&lt;/p&gt;&lt;h3&gt;RevenueCat Setup&lt;/h3&gt;&lt;p&gt;First, we’ll want to set up our purchase in App Store Connect and associate it with our app on the RevenueCat website. For this post, we made a brand new app and added a brand new in-app purchase product in App Store Connect:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;The product setup in App Store Connect&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/31bdfcc145c3e6cefde09e34d0da8b42bca8b8bf-2782x1258.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/31bdfcc145c3e6cefde09e34d0da8b42bca8b8bf-2782x1258.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/31bdfcc145c3e6cefde09e34d0da8b42bca8b8bf-2782x1258.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/31bdfcc145c3e6cefde09e34d0da8b42bca8b8bf-2782x1258.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/31bdfcc145c3e6cefde09e34d0da8b42bca8b8bf-2782x1258.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/31bdfcc145c3e6cefde09e34d0da8b42bca8b8bf-2782x1258.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/31bdfcc145c3e6cefde09e34d0da8b42bca8b8bf-2782x1258.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2782 2782w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/31bdfcc145c3e6cefde09e34d0da8b42bca8b8bf-2782x1258.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;724&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Next, you’ll connect this product to RevenueCat. After signing up for or logging into an account on &lt;a href=&quot;http://revenuecat.com&quot;&gt;their website&lt;/a&gt;, create a new project or use an existing one. Add your App Store app with an app name and bundle ID, and then scroll down and add your App Store Connect API Key using &lt;a href=&quot;https://www.revenuecat.com/docs/app-store-connect-api-key-configuration&quot;&gt;RevenueCat’s instructions&lt;/a&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;The app setup on RevenueCat&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9eab34ee6ad1470719b7173abd875e5543d974f6-2528x2004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/9eab34ee6ad1470719b7173abd875e5543d974f6-2528x2004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/9eab34ee6ad1470719b7173abd875e5543d974f6-2528x2004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/9eab34ee6ad1470719b7173abd875e5543d974f6-2528x2004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/9eab34ee6ad1470719b7173abd875e5543d974f6-2528x2004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/9eab34ee6ad1470719b7173abd875e5543d974f6-2528x2004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/9eab34ee6ad1470719b7173abd875e5543d974f6-2528x2004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2528 2528w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9eab34ee6ad1470719b7173abd875e5543d974f6-2528x2004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1268&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;With this set up, it’s time to associate our Apple in-app purchase with RevenueCat’s notions of products and pricing. Go to &lt;strong&gt;Entitlements&lt;/strong&gt; and add a new one with an ID and a name. Click into the new entitlement and select &lt;strong&gt;Attach&lt;/strong&gt;. Select “Add a new product,” choose “Import Products,” and you’ll see that the Apple in-app purchase product can be automatically pulled in for you! Attach this product to the entitlement, and you should now have both a product and an entitlement for the purchase.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Entitlement setup on RevenueCat&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/ef8d83c9ef2a11b3cb64f110786969d4502e229a-2544x1080.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/ef8d83c9ef2a11b3cb64f110786969d4502e229a-2544x1080.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/ef8d83c9ef2a11b3cb64f110786969d4502e229a-2544x1080.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/ef8d83c9ef2a11b3cb64f110786969d4502e229a-2544x1080.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/ef8d83c9ef2a11b3cb64f110786969d4502e229a-2544x1080.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/ef8d83c9ef2a11b3cb64f110786969d4502e229a-2544x1080.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/ef8d83c9ef2a11b3cb64f110786969d4502e229a-2544x1080.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2544 2544w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/ef8d83c9ef2a11b3cb64f110786969d4502e229a-2544x1080.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;679&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;We’ll take one more step to allow RevenueCat to fetch all of our products for us, so we don’t have to hardcode any of their information into our app. We’ll set up a RevenueCat offering, which represents a selection of products offered to the customer on your paywall, by creating a new one with an identifier and description. Click into the newly created offering and add a package with an identifier and description, and then click into that to attach a product. Once that’s done, we’ve got a single offering with a single product, and we are ready to integrate into visionOS.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/5dad0624aadac2259b7a937c13ed7da83b1156bb-2516x1060.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/5dad0624aadac2259b7a937c13ed7da83b1156bb-2516x1060.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/5dad0624aadac2259b7a937c13ed7da83b1156bb-2516x1060.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/5dad0624aadac2259b7a937c13ed7da83b1156bb-2516x1060.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/5dad0624aadac2259b7a937c13ed7da83b1156bb-2516x1060.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/5dad0624aadac2259b7a937c13ed7da83b1156bb-2516x1060.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/5dad0624aadac2259b7a937c13ed7da83b1156bb-2516x1060.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2516 2516w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/5dad0624aadac2259b7a937c13ed7da83b1156bb-2516x1060.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;674&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;visionOS Integration&lt;/h3&gt;&lt;p&gt;With our project and offerings set up in RevenueCat, we can now integrate the SDK with our visionOS app. To bring the SDK into our app on Xcode 15 (beta 6, as of this writing), we’ll use Swift Package Manager and point it at the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;main&lt;/code&gt; branch to make sure we have the latest updates while in beta.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6e814df6f19ac8203b9954fe8422a7ea2b532b75-3024x2024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/6e814df6f19ac8203b9954fe8422a7ea2b532b75-3024x2024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/6e814df6f19ac8203b9954fe8422a7ea2b532b75-3024x2024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/6e814df6f19ac8203b9954fe8422a7ea2b532b75-3024x2024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/6e814df6f19ac8203b9954fe8422a7ea2b532b75-3024x2024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/6e814df6f19ac8203b9954fe8422a7ea2b532b75-3024x2024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/6e814df6f19ac8203b9954fe8422a7ea2b532b75-3024x2024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/6e814df6f19ac8203b9954fe8422a7ea2b532b75-3024x2024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3024 3024w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6e814df6f19ac8203b9954fe8422a7ea2b532b75-3024x2024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1071&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Now, we can call &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;import Revenue​Cat&lt;/code&gt; to use the SDK! Let’s do so, and then make a super simple loader for the in-app purchase products from RevenueCat that we’ll use in our SwiftUI view. To do that, we’ll make an &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;@Observable&lt;/code&gt; class called &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Product​Loader&lt;/code&gt;, and we’ll call out to RevenueCat to load the offerings that we just set up. Once they are loaded, we’ll get the associated product IDs and store them in a property for later use:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@Observable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;final&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ProductLoader&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; identifiers: [Product.ID] = []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    @MainActor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loadProducts&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        do&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; offerings = &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Purchases.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;shared&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;offerings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; productIDs = offerings.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;current&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;availablePackages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;package&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;                return&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; package&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;storeProduct&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;productIdentifier&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;identifiers&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = productIDs ?? []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        } &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;            // Handle errors&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Finally, we’ll add this loader as a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;@State&lt;/code&gt; property to our view, and we’ll show the new &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Store​View&lt;/code&gt; from Apple with the loaded product IDs when the user taps a button:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ContentView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;View &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    @State&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; isPresentingStore = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    @State&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; productLoader = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ProductLoader&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; body: &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; View {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        NavigationSplitView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;            List&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;                Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Item&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;navigationTitle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Sidebar&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        } &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;detail&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;            VStack&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;                Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;                Button&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    isPresentingStore = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                } &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;                    Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Show In-App Purchases&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;disabled&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(productLoader.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;identifiers&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;navigationTitle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Content&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;padding&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            await&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; productLoader.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;loadProducts&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sheet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;isPresented&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: $isPresentingStore) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;            StoreView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ids&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: productLoader.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;identifiers&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;frame&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;alignment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;front&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;And there you have it! Running this code, we’re now dynamically loading our available products, showing them using Apple’s StoreView on visionOS, and enabling RevenueCat features such as analytics, A/B testing, and cross platform compatibility.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;The test app running on visionOS&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/33d92d9a137d3ab185fc7916611b814d670a32af-2732x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/33d92d9a137d3ab185fc7916611b814d670a32af-2732x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/33d92d9a137d3ab185fc7916611b814d670a32af-2732x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/33d92d9a137d3ab185fc7916611b814d670a32af-2732x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/33d92d9a137d3ab185fc7916611b814d670a32af-2732x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/33d92d9a137d3ab185fc7916611b814d670a32af-2732x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/33d92d9a137d3ab185fc7916611b814d670a32af-2732x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2732 2732w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/33d92d9a137d3ab185fc7916611b814d670a32af-2732x2048.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1199&quot;/&gt;  &lt;/figure&gt; &lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;We hope this guide helps you monetize your exciting new apps for Vision Pro! If you want to learn more about how we can help you implement subscriptions in your app with RevenueCat, &lt;a href=&quot;https://lickability.com/contact&quot;&gt;get in touch&lt;/a&gt; with us.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Brian Capps</author></item><item><title>Detecting Collisions with RealityKit in visionOS</title><link>https://lickability.com/blog/detecting-collisions-with-realitykit-in-visionos/</link><guid isPermaLink="true">https://lickability.com/blog/detecting-collisions-with-realitykit-in-visionos/</guid><description>Our how-to guide</description><pubDate>Thu, 10 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We’ve been experimenting with visionOS lately at Lickability, and it’s been a lot of fun to learn and &lt;a href=&quot;https://twitter.com/lickability/status/1683564649239674880?s=20&quot;&gt;share our progress&lt;/a&gt; online as we go. Detecting whether two entities have collided with each other was one of the first things we wanted to do when developing for Vision Pro. We can accomplish this with RealityKit, but does it look any different on visionOS? Let’s break it down:&lt;/p&gt;&lt;p&gt;First, let’s imagine we have two entities — one cube and one sphere — created with a visionOS project, using volume initial scene, without an immersive space. (I’m using Xcode 15.0 Beta 6 for this demo, all code is subject to change.)&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;a sphere on the left, and cube on the right, the cube is placed 50cm to the right, and both are in a single scene.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/7fc83866ff94917d1c367180c75725f3d1fbf783-1782x1060.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/7fc83866ff94917d1c367180c75725f3d1fbf783-1782x1060.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/7fc83866ff94917d1c367180c75725f3d1fbf783-1782x1060.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/7fc83866ff94917d1c367180c75725f3d1fbf783-1782x1060.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/7fc83866ff94917d1c367180c75725f3d1fbf783-1782x1060.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1782 1782w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/7fc83866ff94917d1c367180c75725f3d1fbf783-1782x1060.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;952&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;We want to know if our objects collide, and a straightforward way to do that would be to move one object into another — so let’s drag the sphere into the cube.&lt;/p&gt;&lt;h3&gt;Adding collision components&lt;/h3&gt;&lt;p&gt;To move the sphere, we need to set up the proper components and add a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Drag​Gesture&lt;/code&gt;. It already has an Input Target component and Collision component set up in the sample, as noted in the session &lt;a href=&quot;https://developer.apple.com/wwdc23/10080&quot;&gt;Build spatial experiences with RealityKit&lt;/a&gt;, and both of these components are necessary to drag an entity. The cube also needs a Collision component so we can detect when the sphere has collided with it.&lt;/p&gt;&lt;p&gt;If you’re not sure which collision shapes to use, you can view them in Reality Composer Pro by going to &lt;strong&gt;Viewport → Check Collision Shapes&lt;/strong&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;the sphere and cube with Collision Shapes turned on in Reality Composer Pro&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a34ef88181505395827c8a2f6683c0bc3eb3ccf6-942x662.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=236 236w, https://cdn.sanity.io/images/nkt6o869/production/a34ef88181505395827c8a2f6683c0bc3eb3ccf6-942x662.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=471 471w, https://cdn.sanity.io/images/nkt6o869/production/a34ef88181505395827c8a2f6683c0bc3eb3ccf6-942x662.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=707 707w, https://cdn.sanity.io/images/nkt6o869/production/a34ef88181505395827c8a2f6683c0bc3eb3ccf6-942x662.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=942 942w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a34ef88181505395827c8a2f6683c0bc3eb3ccf6-942x662.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=942&quot; width=&quot;942&quot; height=&quot;662&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;This isn’t super useful when the collision shapes match the entities, but if we change the collision shape of the sphere to be a box, it’s more defined:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A sphere entity, with a box Collision Shape&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/7a4c1d4ffdf1fdcc91f7288dcbe3a04614bace26-688x544.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=344 344w, https://cdn.sanity.io/images/nkt6o869/production/7a4c1d4ffdf1fdcc91f7288dcbe3a04614bace26-688x544.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=688 688w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/7a4c1d4ffdf1fdcc91f7288dcbe3a04614bace26-688x544.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=688&quot; width=&quot;688&quot; height=&quot;544&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Moving the sphere&lt;/h3&gt;&lt;p&gt;Next, we want to create a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Drag​Gesture&lt;/code&gt; in our &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Content​View&lt;/code&gt; with the following steps:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Reference the sphere entity outside of the make closure in the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Reality​View&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Find the sphere entity in the scene&lt;/li&gt;&lt;li&gt;Target the entity with the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Drag​Gesture&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Change the position of the sphere when the drag changes&lt;/li&gt;&lt;/ol&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ContentView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;View &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    @State&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; sphere: Entity? &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; body: &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; View {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        VStack&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;            RealityView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { content &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;                if&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; scene = &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Entity&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;named&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Scene&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: realityKitContentBundle) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    content.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(scene)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    sphere = content.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;entities&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;first&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;findEntity&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;named&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Sphere&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;gesture&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;                DragGesture&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;targetedToEntity&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(sphere ?? &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Entity&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()) &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;onChanged&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { value &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;                        guard&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; sphere, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; parent = sphere.parent &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;                            return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                        sphere.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;position&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = value.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;convert&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(value.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;location3D&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: parent) &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Now our sphere can move! 🥳&lt;/p&gt;&lt;h3&gt;Detecting collision&lt;/h3&gt;&lt;p&gt;Once our sphere can be moved around, all we have to do to detect collision is subscribe to a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Collision​Event&lt;/code&gt; on the sphere:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Create the subscription to &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Collision​Events.Began&lt;/code&gt; for the sphere.&lt;/li&gt;&lt;li&gt;Store the subscription as a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;@State&lt;/code&gt; property so that it sticks around&lt;/li&gt;&lt;/ol&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ContentView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;View &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    @State&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; sphere: Entity?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    @State&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; subscription: EventSubscription? &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; body: &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;some&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; View {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        VStack&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;            RealityView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { content &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;                // Add the initial RealityKit content&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;                if&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; scene = &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Entity&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;named&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Scene&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: realityKitContentBundle) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    content.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(scene)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    sphere = content.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;entities&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;first&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;findEntity&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;named&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Sphere&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;                    // 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    subscription = content.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: CollisionEvents.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Began&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: sphere) { collisionEvent &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;                        print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;💥 Collision between &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;collisionEvent.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;entityA&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; and &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;collisionEvent.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;entityB&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;gesture&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;                DragGesture&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;targetedToEntity&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(sphere ?? &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Entity&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;onChanged&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { value &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;                        guard&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; sphere, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; parent = sphere.parent &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;                            return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                        sphere.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;position&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = value.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;convert&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(value.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;location3D&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: parent)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;That’s it — now, we’re tracking when the sphere collides with the cube! This event tells us when the two begin colliding, but you can also track when collision ends by subscribing to &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Collision​Events.Ended&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;If you want to see where we went from here, we documented our visionOS progress in a thread &lt;a href=&quot;https://twitter.com/lickability/status/1683564649239674880?s=20&quot;&gt;here&lt;/a&gt; and on &lt;a href=&quot;https://mastodon.social/@lickability/110770793737220784&quot;&gt;Mastodon&lt;/a&gt;. You can also check out our &lt;a href=&quot;https://github.com/Lickability/detecting-collision-visionOS&quot;&gt;sample project&lt;/a&gt; on GitHub.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Looking for a partner to help you develop a visionOS app? &lt;a href=&quot;http://lickability.com/contact&quot;&gt;Let‘s talk.&lt;/a&gt;&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Mikaela Caron, Brian Capps</author></item><item><title>5 years of Clutch</title><link>https://lickability.com/blog/5-years-of-clutch/</link><guid isPermaLink="true">https://lickability.com/blog/5-years-of-clutch/</guid><description>Written in the stars</description><pubDate>Wed, 19 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It‘s been five years since we wrote &lt;a href=&quot;https://lickability.com/blog/working-with-clutch/&quot;&gt;this blog post&lt;/a&gt; about being named one of the top mobile app developers in the country, and even longer since we first started using &lt;a href=&quot;https://clutch.co/profile/lickability&quot;&gt;Clutch&lt;/a&gt; to showcase reviews from our clients.&lt;/p&gt;&lt;p&gt;A lot has happened since then. We‘ve worked with lots of new clients on new projects. We’ve gained new team members, gone fully remote, and gotten together for our first company retreat. We‘ve dedicated many hours to strengthening our design &amp;amp; development skills to continue delivering the best work we can — we’ve even started working with visionOS. 👀&lt;/p&gt;&lt;p&gt;Through all of that, we have remained one of the top app developers on Clutch. In fact, we‘re currently ranked in the Top 10 mobile app developers in New York!&lt;/p&gt;&lt;p&gt;For the uninitiated, Clutch is a platform where clients can publish reviews on B2B companies they‘ve worked with. This means our high placement in these listings is a direct result of our clients writing positive reviews of our work — which means a lot to us.&lt;/p&gt;&lt;p&gt;We‘d like to thank each and every one of our clients whose effort &amp;amp; initiative have helped lift us in the ranks of the app development industry. Your reviews paint a picture of what it’s like to work with us, and are an excellent resource for new clients looking for a trusted partner.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Want to join our roster of satisfied clients? &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Reach out&lt;/a&gt; so we can work together to make something worthy of a five-star review.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Team Lickability</author></item><item><title>Conferences Condensed: WWDC23</title><link>https://lickability.com/blog/conference-condensed-wwdc23/</link><guid isPermaLink="true">https://lickability.com/blog/conference-condensed-wwdc23/</guid><description>The sessions we can&apos;t stop talking about</description><pubDate>Tue, 27 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Another year, another WWDC in the books. This year, we were lucky enough to have a mix of people attending the conference in-person and people meeting in NYC to watch sessions together. We‘ve compiled a list of a few of those sessions that we really loved — &lt;a href=&quot;http://twitter.com/lickability&quot;&gt;let us know&lt;/a&gt; what your favorites were!&lt;/p&gt;&lt;h3&gt;🥧 &lt;a href=&quot;https://developer.apple.com/wwdc23/10037&quot;&gt;Explore pie charts and interactivity in Swift Charts&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;In this session, Apple introduced significant enhancements to the Swift Charts framework. These include the addition of pie and donut charts, and improved interactivity features such as selection and scrolling. The new SectorMark, integral for creating visually captivating pie and donut charts, enables effortless depiction of part-to-whole data relationships. Moreover, the enriched interactivity features provide a more intuitive and user-friendly way to navigate and explore data. This addition equips developers with a fresh approach to present information in an engaging, user-friendly manner. As a result, Apple has really rounded out the SwiftCharts framework, cementing it as a more comprehensive and robust toolkit for data representation.&lt;/p&gt;&lt;h3&gt;🚀 &lt;a href=&quot;https://developer.apple.com/wwdc23/10160&quot;&gt;Demistify SwiftUI Performance&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;During WWDC week, I focused my attention primarily on SwiftUI updates while waiting for the visionOS SDK to become available. While there were many fantastic sessions on our favorite UI framework, my favorite was &lt;strong&gt;Demystify SwiftUI Performance&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;Lately, “Demystify” sessions have been incredibly strong, so much so that I always search the session list once the names become available each year for “demystify”, and prioritize what I find. &lt;strong&gt;Demystify SwiftUI Performance&lt;/strong&gt; is no exception to this recent trend. It offers another peek behind the curtain to better understand minor code level changes and their impact on your app’s performance. SwiftUI &lt;em&gt;is&lt;/em&gt; mysterious, for better or for worse. Many developers we work with still struggle to wrap their heads around result builders, and at many times, SwiftUI feels like magic to those of us who have spent more than a decade writing mobile UI imperatively. While this magic can be great for newcomers and for simple use cases, I strongly recommend taking any opportunity to wash some of it away and develop a deeper understanding of how things actually work under the hood.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Demystify SwiftUI Performance&lt;/strong&gt; joins our list of “mandatory prerequisites” that we share with any developers we work with on SwiftUI projects, alongside its excellent, earlier counterpart, &lt;a href=&quot;https://developer.apple.com/wwdc21/10022&quot;&gt;Demystify SwiftUI&lt;/a&gt; from WWDC21 (I wrote about this session &lt;a href=&quot;https://lickability.com/blog/conferences-condensed-wwdc21/#demystify-swiftui&quot;&gt;here&lt;/a&gt;). Even if you’re a SwiftUI expert, I urge you to watch this year’s session, as you’re bound to learn something new that will help to shape the way you write UI.&lt;/p&gt;&lt;p&gt;&lt;em&gt;— Michael Liberatore&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;📸 &lt;a href=&quot;https://developer.apple.com/wwdc23/10107&quot;&gt;Embed the Photos Picker in your app&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;If your app deals at all with a user’s photo library, even if you only need access for them to choose a profile photo, you’re likely going to watch this session. Apple has done a fantastic job with the new APIs for customizing the photo picking experience that’s sure to make your users’ experiences more streamlined and less fatiguing from permission prompts. Embedding the photo picking experience into your own views is so simple, and with Apple-provided subtle user messaging around what your app actually has access to, I think users will be much happier all around. This short session demos a lot of the embedded photo picker’s customization options, and after viewing, I’m sure you’ll come away wanting to upgrade your photo-picking code right away.&lt;/p&gt;&lt;p&gt;&lt;em&gt;— Michael Liberatore&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;✨ &lt;a href=&quot;https://developer.apple.com/wwdc23/10158&quot;&gt;Animate with springs&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;This session was probably my biggest surprise of WWDC23. Often Friday sessions are a bit less foundational to new technologies than ones earlier in the week, but this one was jam-packed with incredibly detailed and useful information about how spring animations work and some of the nice quality-of-life changes for them coming in the next releases. Jacob expertly employs straightforward examples at just the right times to show how complex topics like springs — and new APIs like gesture velocity — can improve animations across any app. He‘s able to describe the new spring animation APIs that make it easier than ever to get a natural-feeling spring animation, while also going under the hood to describe the math behind it all without putting you to sleep. The beauty of a well-done WWDC talk is taking complex things that feel magical and describing how they work in an understandable and memorable way, and that’s what makes this one so good.&lt;/p&gt;&lt;p&gt;&lt;em&gt;— Brian Capps&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;🥽 &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10203/&quot;&gt;Develop your first immersive app&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;The newest platform by Apple, visionOS! If you’re wanting to get started with spatial computing this is the place to start. Peter goes through every step to get started from creating a new Xcode project, how to use the Simulator for Vision Pro, using Reality Composer Pro, and lastly using entity targeting.&lt;/p&gt;&lt;p&gt;I love how this is truly an introduction that you can follow without watching any other visionOS sessions. He explains new vocabulary like windows and volumes, and notes which sessions to watch to dive deeper into each topic. Throughout the session he notes best practices for visionOS, like always starting the user in a window with clear entry and exit controls so people can decide when to be more immersed in the content. As opposed to moving them into an immersive space without their knowledge.&lt;/p&gt;&lt;p&gt;I’m super excited to use the Vision Pro after just seeing the possibilities with the simulator.&lt;/p&gt;&lt;p&gt;&lt;em&gt;— Mikaela Caron&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;🎨 &lt;a href=&quot;https://developer.apple.com/wwdc23/10115&quot;&gt;Design with SwiftUI&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;The debate about “should designers code” has been raging for years. The question has sparked so many Twitter threads from some of the most insufferable thought leaders silicon valley has to offer. This session seemed like the loudest “Yes” from Apple to date.&lt;/p&gt;&lt;p&gt;Even more than a “yes”, it‘s about &lt;strong&gt;embracing the world of dynamic prototyping&lt;/strong&gt;. It‘s not about designers becoming full-fledged developers or taking over coding responsibilities, it’s about using SwiftUI‘s dynamism in ways static tools like Figma cannot match.&lt;/p&gt;&lt;p&gt;Static mockups are great and I am overjoyed that Apple now has &lt;a href=&quot;http://figma.com/community/file/1248375255495415511&quot;&gt;official design resources for Figma&lt;/a&gt;, but a prototype is worth a thousand words. And there‘s no better place to prototype than in the environment your product will actually be running in, where you can make it as rich as you want. Haptics, device sensors, shaders(!!), it’s all there. The only question is how far you want the prototype to go.&lt;/p&gt;&lt;p&gt;The future of interface design is backed by code written by designers. I apologize in advance to anyone who has to review my messy PRs.&lt;/p&gt;&lt;p&gt;&lt;em&gt;— Sam Gold&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;🌏 Apple Developer Center session&lt;/h3&gt;&lt;p&gt;My favorite session at WWDC 2023 was the in-person session presented at the Apple Developer Center. While it wasn’t recorded, I took &lt;a href=&quot;https://publish.obsidian.md/matthewbischoff/wwdc/2023/Apple+Developer+Center+(Afternoon+Session)&quot;&gt;notes on it and made them available here&lt;/a&gt; for anyone who wasn’t able to attend in person. In the session, John Geleynse introduced everyone to the Developer Center and its capabilities on a virtual tour. Then, Apple engineers from the visionOS team gave us a deeper look a the visionOS SDK and did live demos of building visionOS apps, using ARKIt, and RealityKit. We even got to see how Reality Composer Pro works and how to integrate hand tracking into full space experiences.&lt;/p&gt;&lt;p&gt;Afterward, we all got to chat with the evangelists and engineers over some light refreshments and explore a little more of the beautiful new space built for developers to collaborate directly with Apple.&lt;/p&gt;&lt;p&gt;&lt;em&gt;— mb Bischoff&lt;/em&gt;&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;If you want help getting your app ready for iOS 17, or want to build something for visionOS, &lt;a href=&quot;http://lickability.com/contact&quot;&gt;get in touch&lt;/a&gt;!&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Team Lickability</author></item><item><title>How to install iOS 17 simulators in Xcode 15</title><link>https://lickability.com/blog/how-to-install-ios-17-simulators-in-xcode-15/</link><guid isPermaLink="true">https://lickability.com/blog/how-to-install-ios-17-simulators-in-xcode-15/</guid><description>A quick video tutorial</description><pubDate>Wed, 07 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Since the publishing of this post, official &lt;a href=&quot;https://developer.apple.com/documentation/xcode/installing-additional-simulator-runtimes&quot;&gt;Apple docs&lt;/a&gt; have been published on this topic, which also offer instructions on using &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;xcrun simctl runtime add&lt;/code&gt; from the command line to install simulator runtimes, which may save you some time.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Shell&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;xcode-select&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -s&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; /Applications/Xcode-beta.app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;xcodebuild&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -runFirstLaunch&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;xcrun&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; simctl&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; runtime&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;~/Downloads/ios_17_beta_simulator_runtime.dmg&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;    &lt;/aside&gt;&lt;p&gt;If you‘re like me and download new Xcode versions from the developer portal, you may have run into trouble this time around. Xcode 15 comes in multiple parts: Xcode itself and the simulators you want to install. Unfortunately, I wasn’t sure where I was supposed to install those simulators.&lt;/p&gt;&lt;p&gt;After a lot of googling, I finally found an answer in the &lt;a href=&quot;https://developer.apple.com/forums/thread/730997?answerId=754819022#754819022&quot;&gt;developer forums&lt;/a&gt; that worked for me, so I put together a quick video to help other people out.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Right click on the Xcode 15 Beta application and select &lt;strong&gt;Show Package Content&lt;/strong&gt;.&lt;/li&gt;&lt;li&gt;Double click to open the &lt;strong&gt;iOS_17_beta_Simulator_Runtime.dmg&lt;/strong&gt; file&lt;/li&gt;&lt;li&gt;Inside the simulator dmg file, find the &lt;strong&gt;Runtime&lt;/strong&gt; folder. This is the folder we are going to copy into Xcode.&lt;/li&gt;&lt;li&gt;Place the &lt;strong&gt;Runtime&lt;/strong&gt; folder at &lt;strong&gt;Developer → Platforms → iPhoneOS.platform → Library → Developer → CoreSimulator → Profiles&lt;/strong&gt;inside the Xcode 15 beta &lt;strong&gt;Show Package Contents&lt;/strong&gt;&lt;ol&gt;&lt;li&gt;If you move the &lt;strong&gt;Xcode-beta&lt;/strong&gt; application to the &lt;strong&gt;Application&lt;/strong&gt; folder, you can use Finder to go to &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;/Applications/Xcode-beta.app/Contents/Developer/Platforms/i​Phone​OS.platform/Library/Developer/Core​Simulator/Profiles&lt;/code&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;Restart Xcode if you already have it open, and your iOS simulators will be there&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;And just like that, your simulators are installed.&lt;/p&gt;&lt;p&gt;If you want, you can also redownload the simulators through Xcode instead of doing this — but I found it failed a lot. Hopefully, this will all get smoothed out once everyone isn‘t trying to download everything at the same time. 😅&lt;/p&gt; </content:encoded><author>Michael Amundsen</author></item><item><title>Our WWDC23 hopes and dreams</title><link>https://lickability.com/blog/our-wwdc23-hopes-and-dreams/</link><guid isPermaLink="true">https://lickability.com/blog/our-wwdc23-hopes-and-dreams/</guid><description>Here&apos;s what we want to see from Apple this year</description><pubDate>Thu, 01 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It‘s just about time to start thinking about what we might see at Apple’s developer conference next week! We asked our team to share their biggest wishes and predictions for the event, from SwiftUI APIs to interactive widgets on iOS. Here are our picks — what are yours?&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://mastodon.social/@mliberatore&quot;&gt;Michael Liberatore&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;From a development perspective, the things that I hope for the most this year are a holistic revamp of navigation APIs in SwiftUI, overhauled error reporting in Xcode to mitigate false errors sticking around, and flooding Xcode Cloud with features to make it as compelling as Buddybuild was. I would also love to see Apple’s answer to the rise in large language models, and whether Siri will become super powered.&lt;/p&gt;&lt;p&gt;For keynote goodies, I’d love to see an iPhone SE in the 13 mini form factor, and compelling reasons to be excited about the much rumored headset. I also want to &lt;a href=&quot;https://cottonbureau.com/p/TR4KZV/shirt/mac-pro-believe&quot;&gt;BELIEVE&lt;/a&gt; that the Mac Pro is still coming!&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://twitter.com/mb&quot;&gt;mb Bischoff&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;I’m hoping that in addition to new Mac hardware and the rumored headset, Apple spends this year at WWDC working on adding quality-of-life features across their platforms.&lt;/p&gt;&lt;p&gt;On iOS, my wishlist includes interactive widgets, a real Passwords app, snoozing and scheduling in mail, and redesigns of the Home Screen and Calendar app. I’m also hoping this is the year for custom watch faces on watchOS.&lt;/p&gt;&lt;p&gt;For developers, it’d be great to see a GitHub Copilot-like feature integrated into Xcode that preserves developer privacy, a version of Xcode for iPad, and more effort to make SwiftUI reach API parity with UIKit.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://hachyderm.io/@samhenrigold&quot;&gt;Sam Gold&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;I’ve got four weird feature requests for things that have been bugging me for a while.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/958964fa0e0e9fa115783928532b2604caac09bb-1962x1174.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/958964fa0e0e9fa115783928532b2604caac09bb-1962x1174.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/958964fa0e0e9fa115783928532b2604caac09bb-1962x1174.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/958964fa0e0e9fa115783928532b2604caac09bb-1962x1174.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/958964fa0e0e9fa115783928532b2604caac09bb-1962x1174.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1962 1962w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/958964fa0e0e9fa115783928532b2604caac09bb-1962x1174.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;957&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;On iOS, I’ve had this feedback (Radar 49404087, FB7830265) open for years: use a multiply blend mode for the highlighter annotation tool. Right now, it just overlays highlighter ink with a dropped opacity, which makes text look all muddy: &lt;/p&gt;&lt;p&gt;A great addition to the system-wide share sheet would be a way to strip out tracking parameters and boiler-plate text when sharing links and text from apps (FB9845096). Given Apple’s focus on privacy over the last few years, this feels like a no-brainer. Here’s what TikTok gives me to share around — it’s absurd:&lt;/p&gt;&lt;p&gt;On the Mac, I’d love a way to screen record at lower quality (FB12070699). I often want to do a quick screen recording to text to someone or send in Slack, but it defaults to a super high quality, and I have to either wait for a 60MB video to upload to Slack or open the terminal to run it through ffmpeg. Just gimme a Low/Medium/High quality picker in the Command+Shift+5 floating bar!&lt;/p&gt;&lt;p&gt;Finally, in the watchOS Control Center, just show me the battery percent of my phone (FB12069584). If I’m on the couch and my phone is charging across the room, I want to see when I can unplug my phone.&lt;/p&gt;&lt;p&gt;That’s it. I’d put up a PR of these myself but Apple told me I’m not allowed to look at the source code for their operating systems. Whatever.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://twitter.com/jilliangmeehan&quot;&gt;Jillian Meehan&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Since I’m not a developer, I mostly look forward to the exciting iOS updates every WWDC — I like the fun, flashy stuff. I don’t have many specific wishes (although I’m seconding all of mb’s QOL requests) but I’m &lt;em&gt;very&lt;/em&gt; interested in seeing what kind of enhancements widgets get across the board.&lt;/p&gt;&lt;p&gt;As a recent Reminders convert, a true dream come true for me would be for scheduled reminders to show up in Calendar. (Yes, I know Fantastical has this feature. I love it. I want more of it.) And why stop there? I want to be able to easily attach things from Mail and Notes and Files to my reminders. Apple has some pretty great apps, but I think it’s a shame that they don’t play as well together as they could.&lt;/p&gt;&lt;p&gt;One more thing: I’ve heard the rumors about a new journaling app, and I’m all for it. I’ve never really clicked with any other journaling app, so I’m curious to see what they’ll do.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;What are you hoping to see at WWDC next week? &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;Let‘s talk about it!&lt;/a&gt;&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Team Lickability</author></item><item><title>Conferences Condensed: iOS Conf SG</title><link>https://lickability.com/blog/conferences-condensed-ios-conf-sg/</link><guid isPermaLink="true">https://lickability.com/blog/conferences-condensed-ios-conf-sg/</guid><description>+ one of my own talks!</description><pubDate>Tue, 02 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;To kick off 2023, I headed on a time-traveling journey to Singapore, flying across multiple time zones and over 9,000 miles! &lt;a href=&quot;https://iosconf.sg/&quot;&gt;iOS Conf SG&lt;/a&gt; brings together developers from around the world to learn from one another and find opportunities for collaboration. I had so many exciting new experiences on this trip, and made lots of new memories—here are some of my favorites.&lt;/p&gt;&lt;h3&gt;🚀 Thursday: Async/Await Workshop &amp;amp; Kickoff&lt;/h3&gt;&lt;p&gt;On Thursday, we started with a workshop! &lt;a href=&quot;https://twitter.com/dimsumthinking&quot;&gt;Daniel Steinberg&lt;/a&gt; walked us through how async / await works and how it’s used in different scenarios. We had a lovely lunch break, and then got right into the opening kickoff.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://twitter.com/endesignonline&quot;&gt;Erin Weigel&lt;/a&gt;’s talk, &lt;a href=&quot;https://youtu.be/RmMrvyso8zg&quot;&gt;Experimentation for Engineers&lt;/a&gt;, really made us think about how we can contribute to both the customer experience and business results as engineers. She talked about the linear workflow vs. conversion design process, and how A/B testing can lead to unexpected results.&lt;/p&gt;&lt;p&gt;Later, &lt;a href=&quot;https://twitter.com/jordibruin&quot;&gt;Jordi Bruin&lt;/a&gt; wrapped up the day by showing us &lt;a href=&quot;https://youtu.be/Nz4R517_bVk&quot;&gt;his 2-2-2 method&lt;/a&gt; for creating apps, which he’s used to ship over 20 apps in two years. The method goes like this:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Spend two hours on a very basic prototype proving that you can make the app, with very minimal functionality.&lt;/li&gt;&lt;li&gt;Spend two days refining that prototype to be more usable for yourself and possibly others.&lt;/li&gt;&lt;li&gt;Lastly, spend two weeks to round it out and prepare to launch to the App Store.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;I’ll be using this method from now on to validate my ideas without getting caught up in the details.&lt;/p&gt;&lt;h3&gt;☕️ Friday: Talks, Demos, &amp;amp; Coffee&lt;/h3&gt;&lt;p&gt;Friday was the big day! I started the day with my talk, &lt;a href=&quot;https://youtu.be/VZjJ691597M&quot;&gt;What’s an App Without Data?&lt;/a&gt;—where I explored where to save data both locally and in the cloud, broke down some of the different situations you might run into, and went over how to choose the right database for your app.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Mikaela giving her talk&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/352da6859eea29f53475952ed2995f5fa8700bf7-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/352da6859eea29f53475952ed2995f5fa8700bf7-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/352da6859eea29f53475952ed2995f5fa8700bf7-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/352da6859eea29f53475952ed2995f5fa8700bf7-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/352da6859eea29f53475952ed2995f5fa8700bf7-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/352da6859eea29f53475952ed2995f5fa8700bf7-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/352da6859eea29f53475952ed2995f5fa8700bf7-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/352da6859eea29f53475952ed2995f5fa8700bf7-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3024 3024w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/352da6859eea29f53475952ed2995f5fa8700bf7-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;2133&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A table with lots of coffee&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f68f34253b4ceffe4f5c37fc49a9f33d8c3da760-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/f68f34253b4ceffe4f5c37fc49a9f33d8c3da760-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/f68f34253b4ceffe4f5c37fc49a9f33d8c3da760-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/f68f34253b4ceffe4f5c37fc49a9f33d8c3da760-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/f68f34253b4ceffe4f5c37fc49a9f33d8c3da760-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/f68f34253b4ceffe4f5c37fc49a9f33d8c3da760-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/f68f34253b4ceffe4f5c37fc49a9f33d8c3da760-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/f68f34253b4ceffe4f5c37fc49a9f33d8c3da760-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3024 3024w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f68f34253b4ceffe4f5c37fc49a9f33d8c3da760-3024x4032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;2133&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p data-astro-cid-c6ccksbc&gt;A table with lots of coffee&lt;/p&gt; &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Afterwards, &lt;a href=&quot;https://twitter.com/twostraws&quot;&gt;Paul Hudson&lt;/a&gt; &lt;a href=&quot;https://youtu.be/gqZmsIkV0Qc&quot;&gt;demonstrated to us&lt;/a&gt; that (for now 😅) ChatGPT is &lt;em&gt;not&lt;/em&gt; going to be taking our jobs—after three iterations, it wasn’t able to recreate a clock SwiftUI view that mimics the one on the Home Screen.&lt;/p&gt;&lt;p&gt;After a quick coffee break, &lt;a href=&quot;https://twitter.com/twostraws&quot;&gt;Paul Hudson&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/tundsdev&quot;&gt;Tunde Adegoroye&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/endesignonline&quot;&gt;Erin Weigel&lt;/a&gt;, and I were on a panel called &lt;a href=&quot;https://youtu.be/ZxJeNq4ODZQ&quot;&gt;More Than Code: Rooting Yourself in the iOS Community&lt;/a&gt;. We discussed being content creators, contributing to the community, and how anyone at any skill level can get involved. mb Garlington, a developer who has unfortunately passed on, was a great example of someone who had a big impact—he hadn’t spoken at a conference, published a book, or created a YouTube channel, but he was someone who was willing to help any other developers get into the tech community. You don’t have to be a speaker or a content creator—you just have to want to help others.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A photo of the panel&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/583598b460dd918adc3abe30b18b9ac30e382276-2048x1536.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/583598b460dd918adc3abe30b18b9ac30e382276-2048x1536.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/583598b460dd918adc3abe30b18b9ac30e382276-2048x1536.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/583598b460dd918adc3abe30b18b9ac30e382276-2048x1536.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/583598b460dd918adc3abe30b18b9ac30e382276-2048x1536.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/583598b460dd918adc3abe30b18b9ac30e382276-2048x1536.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2048 2048w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/583598b460dd918adc3abe30b18b9ac30e382276-2048x1536.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;To round out the conference, &lt;a href=&quot;https://twitter.com/b3ll&quot;&gt;Adam Bell&lt;/a&gt; gave a great overview and implementation of &lt;a href=&quot;https://youtu.be/Vbpr9xp7XKk&quot;&gt;building responsive interactions&lt;/a&gt;, showing us that it’s the details that can make your app feel more responsive and polished. Having unresponsive scrolling and gestures, or not reacting to touches immediately, can make an app feel broken—Adam showed us how to fix those issues.&lt;/p&gt;&lt;h3&gt;🤝 Community&lt;/h3&gt;&lt;p&gt;iOS Conf SG was voted as the Best Conference in the &lt;a href=&quot;https://www.hackingwithswift.com/awards/2021&quot;&gt;Hacking With Swift community awards&lt;/a&gt; in 2021—and for a good reason! There were so many amazing talks that I can’t even mention them all here, but you can check out &lt;a href=&quot;https://youtube.com/playlist?list=PLED4k3CZkY9RBYTMNziVhwXGepdcUIz8B&quot;&gt;their playlist&lt;/a&gt; to see the rest.&lt;/p&gt;&lt;p&gt;The community emphasis throughout the conference was unmatched. The organizers started and ended the conference emphasizing the importance of socializing with others and making new connections. You never know—you might find your next business partners or beta testers at a conference!&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Mikaela is giving another talk at &lt;a href=&quot;https://deepdishswift.com/&quot;&gt;Deep Dish Swift&lt;/a&gt;this week! Make sure to cheer her on online if you‘re not attending in person. 🙏&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Mikaela Caron</author></item><item><title>We’re a RevenueCat partner!</title><link>https://lickability.com/blog/revenuecat/</link><guid isPermaLink="true">https://lickability.com/blog/revenuecat/</guid><description>Certified in-app subscription experts 😎</description><pubDate>Thu, 13 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Lickability is now a certified &lt;a href=&quot;https://www.revenuecat.com/partners/agency/lickability&quot;&gt;Premier Partner&lt;/a&gt; of RevenueCat’s &lt;a href=&quot;https://www.revenuecat.com/blog/company/agencies-ready-to-join-the-revenuecat-partner-program/&quot;&gt;Agency Partner Program&lt;/a&gt;—which means we can provide even better service to our clients, helping them to integrate and manage in-app subscriptions more efficiently.&lt;/p&gt;&lt;h3&gt;So, what is RevenueCat?&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://www.revenuecat.com/&quot;&gt;RevenueCat&lt;/a&gt; is a powerful tool for app developers looking to add subscriptions to their app. It takes care of all the complexities of in-app purchases, such as managing subscriptions, handling billing, and providing detailed analytics, so developers can focus on building their app. With RevenueCat, app developers can easily add subscriptions to their app and start generating revenue.&lt;/p&gt;&lt;h3&gt;How can we help you?&lt;/h3&gt;&lt;p&gt;If you’re an app developer looking to add subscriptions to your app, Lickability’s got you covered. With our expertise in app development and our new partnership with RevenueCat, we can help you integrate and manage subscriptions in your app seamlessly. We’ll work with you to understand your needs and help you choose the best subscription model for your app.&lt;/p&gt;&lt;p&gt;Not convinced yet? Here’s what our friends at RevenueCat have to say:&lt;/p&gt;&lt;blockquote&gt;The mobile subscriptions market is growing. According to &lt;a href=&quot;http://data.ai/&quot;&gt;data.ai&lt;/a&gt;, worldwide app revenue grew by 8% in 2022, while in the US it grew by 16%. This growth showcases the resilience of the subscription business model, even in the face of the challenges seen in other corners of the tech industry. What is clear from the data is that there is an enormous growth opportunity for mobile businesses to take advantage of recurring revenue models — and more mobile businesses than ever will be looking for easy and frictionless ways to implement recurring subscriptions.&lt;/blockquote&gt;&lt;p&gt;&lt;em&gt;– &lt;a href=&quot;https://www.revenuecat.com/blog/company/agencies-ready-to-join-the-revenuecat-partner-program/&quot;&gt;Truman So&lt;/a&gt;, Head of Partnerships at RevenueCat&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;Get in touch!&lt;/h3&gt;&lt;p&gt;We’re super happy to be a Premier Partner with RevenueCat, and can’t wait to see what opportunities this collaboration will bring. &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Get in touch&lt;/a&gt; with us today and learn more about how we can help you implement subscriptions in your app with RevenueCat? Let’s work together to make your app successful.&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Conferences Condensed: 360iDev 2022</title><link>https://lickability.com/blog/conferences-condensed-360idev-2022/</link><guid isPermaLink="true">https://lickability.com/blog/conferences-condensed-360idev-2022/</guid><description>The final year 😢</description><pubDate>Fri, 10 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In August, we attended &lt;a href=&quot;https://360idev.com&quot;&gt;360iDev&lt;/a&gt;, a long running iOS and macOS developer conference based in Denver, CO. Regrettably, this was the last 360iDev conference after over a decade. Here’s a look back at some of our favorite talks from the conference and what we learned from them!&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Mikaela and Daisy sitting around a table with friends from 360iDev&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9a4d7fdd22f7354ff838bae41dad61b32fe3acc6-2048x1150.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/9a4d7fdd22f7354ff838bae41dad61b32fe3acc6-2048x1150.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/9a4d7fdd22f7354ff838bae41dad61b32fe3acc6-2048x1150.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/9a4d7fdd22f7354ff838bae41dad61b32fe3acc6-2048x1150.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/9a4d7fdd22f7354ff838bae41dad61b32fe3acc6-2048x1150.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/9a4d7fdd22f7354ff838bae41dad61b32fe3acc6-2048x1150.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2048 2048w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9a4d7fdd22f7354ff838bae41dad61b32fe3acc6-2048x1150.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;898&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;🔌 &lt;a href=&quot;https://www.youtube.com/watch?v=1GcU70xZ-P8&quot;&gt;Generating Code and Other Mischief with Swift Package Manager Plugins&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://twitter.com/designatednerd&quot;&gt;&lt;strong&gt;Ellen Shapiro&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Ellen started off by giving us an overview of how we got here. How did plugins become part of the package manager, rather than the IDE? In 2013, there was Alcatraz; in 2016, with Xcode 8, there were source extensions; and finally, in 2022, we now have command plugins and build tool plugins with Xcode 14.&lt;/p&gt;&lt;p&gt;Command plugins are the simpler of the two. They can be invoked either by the command line or from the Xcode menu. When using command plugins, be sure you know what they’re doing, because they can change your code in a way you may not have intended!&lt;/p&gt;&lt;p&gt;Build tool plugins are designed to be run automatically on every build—it’s a package embedded into &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;.xcodeproj&lt;/code&gt;, and the plugin is then added to the Package.swift. This is what people are generally referring to when they talk about “Xcode plugins.”&lt;/p&gt;&lt;blockquote&gt;Don’t build your plugin and the package you’re testing it against in the same repo!&lt;/blockquote&gt;&lt;p&gt;There are still quite a few limitations with plugins. First, as a user, &lt;em&gt;always&lt;/em&gt; make sure you know what plugins are doing—don’t just click “Enable All” when running them. And when developing them, keep in mind that there’s no UI for your users, you can’t make network calls, and you can’t share data between plugins.&lt;/p&gt;&lt;p&gt;&lt;em&gt;— Mikaela&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;☁️ &lt;a href=&quot;https://youtu.be/jNSdYE1nL8o&quot;&gt;Choosing the Right Cloud Database For Your Indie iOS App&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://twitter.com/mikaela__caron&quot;&gt;&lt;strong&gt;Mikaela Caron&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Mikaela Caron on stage for her talk at 360iDev&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/33224bd5b84949665a80ef60b091fb34c58bebb0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/33224bd5b84949665a80ef60b091fb34c58bebb0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/33224bd5b84949665a80ef60b091fb34c58bebb0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/33224bd5b84949665a80ef60b091fb34c58bebb0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/33224bd5b84949665a80ef60b091fb34c58bebb0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/33224bd5b84949665a80ef60b091fb34c58bebb0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/33224bd5b84949665a80ef60b091fb34c58bebb0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/33224bd5b84949665a80ef60b091fb34c58bebb0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/33224bd5b84949665a80ef60b091fb34c58bebb0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1067&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Surprise: we sponsored part of 360iDev by contributing this talk, from myself!&lt;/p&gt;&lt;p&gt;In my talk, I reviewed what a database is, and the big decisions that need to be made when choosing which one to use as an indie iOS developer. I compared three different cloud databases: Firebase, AWS Amplify, and Vapor with PostgreSQL. The question &lt;em&gt;“how to choose the right database”&lt;/em&gt; is based on three main considerations: pricing, data, and authentication.&lt;/p&gt;&lt;p&gt;After evaluating each of the cloud databases with those considerations, we still have that question—&lt;em&gt;“which database do we use?”&lt;/em&gt; To better understand when to choose which database, I reviewed different scenarios an indie app developer might face, and what the best cloud database option for each scenario might be.&lt;/p&gt;&lt;p&gt;Wrapping up the talk, the question remains—*“which one is the &lt;strong&gt;best*&lt;/strong&gt;&lt;em&gt;for an indie dev?”&lt;/em&gt; Personally, I chose Firebase, because AWS Amplify’s pricing is confusing and Vapor requires more backend knowledge and maintenance that may not be the best use of your time.&lt;/p&gt;&lt;blockquote&gt;In the end though, it doesn’t really matter—as Justin (the keynote speaker) said, “all of the code is ephemeral, you can choose whichever you want,” and you’re probably going to change it later.&lt;/blockquote&gt;&lt;p&gt;&lt;em&gt;— Mikaela&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;🔋 &lt;a href=&quot;https://www.youtube.com/watch?v=OgCjn_CjRhk&quot;&gt;SwiftUI Performance for Demanding Apps&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://twitter.com/avielgr&quot;&gt;&lt;strong&gt;Aviel Gross&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A graphic from Aviel&amp;#x27;s talk&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/685009c561344b0a0a1a9258221bafa5571eaa67-1722x1004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/685009c561344b0a0a1a9258221bafa5571eaa67-1722x1004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/685009c561344b0a0a1a9258221bafa5571eaa67-1722x1004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/685009c561344b0a0a1a9258221bafa5571eaa67-1722x1004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/685009c561344b0a0a1a9258221bafa5571eaa67-1722x1004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1722 1722w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/685009c561344b0a0a1a9258221bafa5571eaa67-1722x1004.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;933&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;It’s hard to believe SwiftUI is already three years old, and now has relatively mature APIs that can be shipped in production. SwiftUI is highly performant with the right architecture in place!&lt;/p&gt;&lt;p&gt;Aviel’s talk was about how we can shift our mindset from imperative to declarative thinking. He also elaborated on how existing UIKit apps are structured, along with how to leverage some of the best features of SwiftUI—from publishing changes with &lt;a href=&quot;https://developer.apple.com/documentation/combine/observableobject&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Observable​Object&lt;/code&gt;&lt;/a&gt; for state management to avoiding redundant view updates. This talk is chock full of good SwiftUI resources and patterns!&lt;/p&gt;&lt;p&gt;&lt;em&gt;— Daisy&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;⏩ &lt;a href=&quot;https://www.youtube.com/watch?v=spb5wP4kDmw&quot;&gt;Automating with Fastlane: Tips, Tricks, and Best Practices&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://twitter.com/joshdholtz&quot;&gt;&lt;strong&gt;Josh Holtz&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Josh is truly an inspiration for anyone that has a fear of public speaking. As a person with a disability—a bad case of migraines—I &lt;em&gt;so&lt;/em&gt; appreciated his candidness when he mentioned his speech impediment. His talk was incredibly well done!&lt;/p&gt;&lt;p&gt;If you’re an iOS or Android developer, you’ve probably heard of &lt;a href=&quot;https://docs.fastlane.tools&quot;&gt;Fastlane&lt;/a&gt; at some point. Josh went over the basics of Fastlane and how to use it within Xcode and the CLI. The most notable part of this talk is the &lt;a href=&quot;https://docs.fastlane.tools/advanced/Fastfile/&quot;&gt;Fastfile&lt;/a&gt;, a Ruby file that takes care of things like incrementing build numbers, archiving an app, capturing screenshots, and uploading to TestFlight and the App Store. Josh gave us some nifty tips on how to make the Fastfile more reusable and composable, all in a live demo that syncs all the necessary code signing certificates and provisioning profiles.&lt;/p&gt;&lt;p&gt;&lt;em&gt;— Daisy&lt;/em&gt;&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;For more about 360iDev, check out our blog post from &lt;a href=&quot;https://lickability.com/blog/conferences-condensed-360idev/&quot;&gt;last year&lt;/a&gt;!&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Team Lickability</author></item><item><title>Redesigning our documents</title><link>https://lickability.com/blog/redesigning-our-contracts/</link><guid isPermaLink="true">https://lickability.com/blog/redesigning-our-contracts/</guid><description>How &amp; why we did it</description><pubDate>Thu, 01 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Between &lt;a href=&quot;https://lickability.com/blog/learning-with-lickability/&quot;&gt;Learn with Lickability&lt;/a&gt; sessions, &lt;a href=&quot;https://lickability.com/blog/conferences-condensed-wwdc22/&quot;&gt;attending conferences&lt;/a&gt;, and asking our friends for advice in our &lt;a href=&quot;https://lickability.com/blog/slack-is-for-friends-too/&quot;&gt;“everybody” Slack channel&lt;/a&gt;, we’re always looking for ways we can level up our game here at Lickability. Here in &lt;em&gt;Salesland&lt;/em&gt; (as we so fondly refer to it in our team meetings), our latest improvement is a ground-up &lt;strong&gt;redesign of all the documents we use during our sales process&lt;/strong&gt;, and today we’d like to walk you though the why‘s, how’s, and (preliminary) results of this project.&lt;/p&gt;&lt;h4&gt;Our sales process&lt;/h4&gt;&lt;p&gt;First, here’s a high level overview of our sales process. We divide the stages of our pipeline as follows:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;📨 Lead comes in—via our website, a personal contact, social media etc.&lt;/li&gt;&lt;li&gt;📞 Initial call to determine if it’s a good fit for both parties&lt;/li&gt;&lt;li&gt;🧮 Estimate the project with our engineers &amp;amp; draft a proposal&lt;/li&gt;&lt;li&gt;🗣 Proposal walkthrough with the client&lt;/li&gt;&lt;li&gt;🤝 Share formal contracts, legal review, and negotiation&lt;/li&gt;&lt;li&gt;✍️ Signatures and project kickoff&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;If you want the full explainer, check out our &lt;a href=&quot;https://lickability.com/blog/how-we-do-sales/&quot;&gt;How We Do Sales&lt;/a&gt; post.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;gif of Glen Ross saying Always Be Closing&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/15b09c5cee549b7237d11006968c0eca32863e87-498x231.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=249 249w, https://cdn.sanity.io/images/nkt6o869/production/15b09c5cee549b7237d11006968c0eca32863e87-498x231.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=498 498w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/15b09c5cee549b7237d11006968c0eca32863e87-498x231.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=498&quot; width=&quot;498&quot; height=&quot;231&quot;/&gt;  &lt;/figure&gt; &lt;h4&gt;The docs &amp;amp; how we use them&lt;/h4&gt;&lt;p&gt;For each of the stages of our sales process, we have a collection of documents that we may send to clients. These include formal contracts with lots of legal and business terms, like our Statements of Work or Services Agreements, and supporting documents like proposals, summaries of our work, or design showcases.&lt;/p&gt;&lt;p&gt;We store the documents in our shared Google Drive as templates, and we customize them for each client. When creating a new document for a client, an account manager fills out the appropriate template with the information needed, and it is reviewed and approved by our operations, engineering, and finance teams using the &lt;a href=&quot;https://www.wrangle.io&quot;&gt;Wrangle&lt;/a&gt; Slack app. If everything checks out, it goes on its way.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot showing how we use the Wrangle Slack app&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/eb94727bc1b7a3d2613eb7c71f2a7afcda3b8135-669x1028.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=335 335w, https://cdn.sanity.io/images/nkt6o869/production/eb94727bc1b7a3d2613eb7c71f2a7afcda3b8135-669x1028.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=669 669w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/eb94727bc1b7a3d2613eb7c71f2a7afcda3b8135-669x1028.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=669&quot; width=&quot;669&quot; height=&quot;1028&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;We present our documents in a variety of ways, but most commonly we walk clients through them together over Zoom. When formal contracts are ready to be reviewed, those are sent out to the appropriate folks either via email (if we expect some legal back and forth) or &lt;a href=&quot;https://www.docusign.com&quot;&gt;DocuSign&lt;/a&gt; (if everyone is happy and ready to sign).&lt;/p&gt;&lt;p&gt;Here’s a list of our most used and important documents:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Proposal:&lt;/strong&gt; Arguably the most important document we will send to a client. This is our chance to explain how we would approach their project, what they can expect to happen as it progresses, and the benefits of choosing Lickability as a partner.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Statement of Work:&lt;/strong&gt; This document is a formal restatement of the proposal and defines important project terms like what’s in scope, what assumptions have been made, and payment terms.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Services Agreement:&lt;/strong&gt; This document goes hand in hand with our Statement of Work and defines important business and legal items such as Intellectual Property, Termination, and Confidentiality&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Amendment:&lt;/strong&gt; This is used when we need to make small changes to a Statement of Work, such as extending the project term or adding folks to a project.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Contractor Agreements:&lt;/strong&gt; We also have a slew of documents we use when working with fellow agencies or independent contractors, and we give them the same care and effort we would a client.&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Why update our docs?&lt;/h4&gt;&lt;p&gt;A question that came up when we were thinking about starting this project was, “why should we spend time doing this?” Our old documents were easy to prepare, clients gave us positive feedback on their simplicity, and we were successfully closing deals with them. However, they were a few years old and showing their age, plus it was a chance to show off our design chops to clients right at the start of our conversations with them.&lt;/p&gt;&lt;p&gt;We’re big on Steve Jobs quotes around here (ever wondered where the name Lickability &lt;a href=&quot;https://youtu.be/pmz30G9TVQw?t=24&quot;&gt;comes from?&lt;/a&gt;), and one was particularly front-of-mind when starting this project:&lt;/p&gt;&lt;blockquote&gt;When you’re a carpenter making a beautiful chest of drawers, you’re not going to use a piece of plywood on the back, even though it faces the wall and nobody will ever see it. You’ll know it’s there, so you’re going to use a beautiful piece of wood on the back. For you to sleep well at night, the aesthetic, the quality, has to be carried all the way through.&lt;/blockquote&gt;&lt;p&gt;Although our Statement of Work documents are &lt;em&gt;barely&lt;/em&gt; marketing tools, we want to show clients that we sweat the small stuff, even if it goes unnoticed. To that end, we put a lot of care into the details: the paper’s subtle red tint, using all the typographic tricks in the book (like the № sign in lieu of “#” or “Number”), and laying out section headers in the margins like magazines do.&lt;/p&gt;&lt;h4&gt;The research, finding inspiration, and the design process&lt;/h4&gt;&lt;p&gt;Finding reference material was challenging, since other companies tend to be fairly cagey about sharing the minutiae on their internal tools. Branching out to find seasoned documents (not just glossy Dribbble templates) from different sectors proved to be difficult, but we were able to find a handful from other agencies we’ve worked with, which was certainly helpful to assess what works and what doesn’t. With those notes in mind, and only a number of guard rails in place, it was time to get to work.&lt;/p&gt;&lt;p&gt;Since we’re a fully-remote team, getting immediate feedback on design drafts was paramount. For this, we used a dedicated Slack channel and a shared Google Drive to keep everything in one place. As Sam made design changes, he called out the ones he felt less certain on and people from all different functions around the company helped suggest changes. Cross-function collaboration is invaluable for design tasks like these—we worked together to tailor the designs to Tom’s presentation style, and our Ops team offered fresh eyes on the copy to make sure the tone wasn’t too sales-y.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot from our Slack showing how we collaborated&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/205aef4546c03a1ecf791821bcfe11acfdca5f1d-1920x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/205aef4546c03a1ecf791821bcfe11acfdca5f1d-1920x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/205aef4546c03a1ecf791821bcfe11acfdca5f1d-1920x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/205aef4546c03a1ecf791821bcfe11acfdca5f1d-1920x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/205aef4546c03a1ecf791821bcfe11acfdca5f1d-1920x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1920 1920w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/205aef4546c03a1ecf791821bcfe11acfdca5f1d-1920x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1280&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;&lt;a href=&quot;https://typographyforlawyers.com/&quot;&gt;Typography for Lawyers&lt;/a&gt; was an invaluable resource throughout the design process. Since Sam’s background is in product design, it helped inform many decisions regarding hierarchy and layout in this different context.&lt;/p&gt;&lt;h4&gt;Old vs. new&lt;/h4&gt;&lt;p&gt;Previously, our documents were in format purgatory—partly Word format, partly Google Docs. This made it difficult to export them nicely as either. Additionally, the formatting was baked in and extremely fragile. For example, bullet points were created using a series of spaces, dashes, and more spaces instead of using standard word processing lists. This meant that any changes could cause the document to reflow in unexpected ways, since line breaks were used instead of just letting lines wrap.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A gif showing that editing lines with forced line-breaks creates unexpected wrapping&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/1b91f955a15c32261a5864db7934777678964d86-1280x1024.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=320 320w, https://cdn.sanity.io/images/nkt6o869/production/1b91f955a15c32261a5864db7934777678964d86-1280x1024.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=640 640w, https://cdn.sanity.io/images/nkt6o869/production/1b91f955a15c32261a5864db7934777678964d86-1280x1024.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=960 960w, https://cdn.sanity.io/images/nkt6o869/production/1b91f955a15c32261a5864db7934777678964d86-1280x1024.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1280 1280w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/1b91f955a15c32261a5864db7934777678964d86-1280x1024.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1280&quot; width=&quot;1280&quot; height=&quot;1024&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Editing lines with forced line-breaks creates unexpected wrapping.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;An image showing invisible characters in our old documents&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c8e2cf3a743984c54411f8191615e4d67533405d-1920x1540.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/c8e2cf3a743984c54411f8191615e4d67533405d-1920x1540.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/c8e2cf3a743984c54411f8191615e4d67533405d-1920x1540.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/c8e2cf3a743984c54411f8191615e4d67533405d-1920x1540.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/c8e2cf3a743984c54411f8191615e4d67533405d-1920x1540.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1920 1920w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c8e2cf3a743984c54411f8191615e4d67533405d-1920x1540.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1283&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Invisible characters in our old documents. Note the forced line breaks after each line and the string of spaces after each header.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Another issue was that they were designed to be printed, but we‘re a digital studio selling digital services to companies that value their digital presence—print is the least important medium at this point. This gave us the opportunity to take some visual liberties and inject more of our brand. We could worry less about printers cutting off the edges or wasting ink with a subtle paper tint color.&lt;/p&gt;&lt;p&gt;All of our new documents are Google Docs-native, making doc-specific feedback a breeze both internally and when we sent it out to third parties for review. (By which we mean lawyers and no other examples.)&lt;/p&gt;&lt;p&gt;We can also use quality-of-life features like Google‘s date chips and the outline view. They’re designed digital-first but gracefully degrade—exporting nicely to Word, Pages, and PDF. They‘re still A4 size so if it &lt;em&gt;does&lt;/em&gt; get printed, printers will know how to handle it (rather than one long, seamless PDF that would print out as a thin strip on a single sheet).&lt;/p&gt;&lt;p&gt;Most importantly, the new docs elevate the client’s brand to the same level as ours, and are unmistakably us.&lt;/p&gt;&lt;figure class=&quot;full-bleed&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;An example of our new documents over top of an example of our old documents&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e66e7c3390f37d372ac212ef32289562b3d6ba25-1920x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=480 480w, https://cdn.sanity.io/images/nkt6o869/production/e66e7c3390f37d372ac212ef32289562b3d6ba25-1920x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=960 960w, https://cdn.sanity.io/images/nkt6o869/production/e66e7c3390f37d372ac212ef32289562b3d6ba25-1920x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1440 1440w, https://cdn.sanity.io/images/nkt6o869/production/e66e7c3390f37d372ac212ef32289562b3d6ba25-1920x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1920 1920w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e66e7c3390f37d372ac212ef32289562b3d6ba25-1920x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1920&quot; width=&quot;1920&quot; height=&quot;1536&quot;/&gt;  &lt;/figure&gt; &lt;h4&gt;Our results&lt;/h4&gt;&lt;p&gt;So, how have our shiny new documents been received? In short, fantastically! Clients tell us they love the simplicity and clarity of how we structure the information, the care and attention to detail in our design, and that we include their logo front and center. All of this combined makes the documents easier to share, present, and communicate their meaning.&lt;/p&gt;&lt;p&gt;Design is never finished, so we’re continuing to refine the templates to meet the needs of ourselves and our clients. Right now, we’re working on adapting the design language of the new documents into an agency overview slide deck. We can’t wait for you to see the final product. You should totally &lt;a href=&quot;https://lickability.com/contact&quot;&gt;reach out to us&lt;/a&gt; if you’d like a personal overview. 😉&lt;/p&gt; </content:encoded><author>Thomas DeVuono, Sam Henri Gold</author></item><item><title>We Like Widgets!</title><link>https://lickability.com/blog/lock-screens/</link><guid isPermaLink="true">https://lickability.com/blog/lock-screens/</guid><description>An updated look at our home screens, lock screens, and watch faces.</description><pubDate>Thu, 03 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It‘s been a while since we shared our &lt;a href=&quot;https://lickability.com/blog/home-sweet-home-screen/&quot;&gt;home screens&lt;/a&gt; and &lt;a href=&quot;https://lickability.com/blog/the-many-watch-faces-of-lickability/&quot;&gt;watch faces&lt;/a&gt; here—so long, in fact, that those blog posts look a bit like time capsules now. So we figured it was time for an update. Here‘s a new look at our digital layouts, including all the widgets and complications we’ve welcomed into our lives in the past few years.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://twitter.com/ThomasDeVuono&quot;&gt;Tom DeVuono&lt;/a&gt;&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of Tom&amp;#x27;s lock screen&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e9417c7ab5b793524dbac557581a870f588280ae-1170x2532.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=293 293w, https://cdn.sanity.io/images/nkt6o869/production/e9417c7ab5b793524dbac557581a870f588280ae-1170x2532.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=585 585w, https://cdn.sanity.io/images/nkt6o869/production/e9417c7ab5b793524dbac557581a870f588280ae-1170x2532.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=878 878w, https://cdn.sanity.io/images/nkt6o869/production/e9417c7ab5b793524dbac557581a870f588280ae-1170x2532.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1170 1170w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e9417c7ab5b793524dbac557581a870f588280ae-1170x2532.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1170&quot; width=&quot;1170&quot; height=&quot;2532&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of Tom&amp;#x27;s home screen&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/99a49e4d25de44759008ae5a6033a827c86085a1-1170x2532.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=293 293w, https://cdn.sanity.io/images/nkt6o869/production/99a49e4d25de44759008ae5a6033a827c86085a1-1170x2532.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=585 585w, https://cdn.sanity.io/images/nkt6o869/production/99a49e4d25de44759008ae5a6033a827c86085a1-1170x2532.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=878 878w, https://cdn.sanity.io/images/nkt6o869/production/99a49e4d25de44759008ae5a6033a827c86085a1-1170x2532.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1170 1170w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/99a49e4d25de44759008ae5a6033a827c86085a1-1170x2532.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1170&quot; width=&quot;1170&quot; height=&quot;2532&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;I like to keep my lock screen pretty simple and avoid widget overload. Weather is my favorite widget and the one I find most useful, so it’s always front and center no matter where I am. For the lock screen, I also added the upcoming meeting widget, which is great for work. I round out the top with UV Index, ’cause &lt;a href=&quot;https://www.youtube.com/watch?v=xuCn8ux2gbs&amp;feature=emb_title&quot;&gt;the sun is a deadly laser&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;For the watch, again, keeping it pretty simple. I really like the new astronomy choices, and the little animations it does when I raise my watch are fun.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of Tom&amp;#x27;s watch face&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/dc52ec7b7fbd01145245a924a24e2c59f6dca6b0-934x934.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=234 234w, https://cdn.sanity.io/images/nkt6o869/production/dc52ec7b7fbd01145245a924a24e2c59f6dca6b0-934x934.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=467 467w, https://cdn.sanity.io/images/nkt6o869/production/dc52ec7b7fbd01145245a924a24e2c59f6dca6b0-934x934.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=701 701w, https://cdn.sanity.io/images/nkt6o869/production/dc52ec7b7fbd01145245a924a24e2c59f6dca6b0-934x934.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=934 934w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/dc52ec7b7fbd01145245a924a24e2c59f6dca6b0-934x934.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=934&quot; width=&quot;934&quot; height=&quot;934&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Home screen layout and widget choices remain the same, just a new wallpaper that matches the lock screen. My home screen is the only page I run— everything that’s not on there I get to with Spotlight search.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://twitter.com/samhenrigold&quot;&gt;Sam Gold&lt;/a&gt;&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of Sam&amp;#x27;s lock screen&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/cf18a4f4d06a9ed74ccc2bb4ee509bc30e1111e6-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=295 295w, https://cdn.sanity.io/images/nkt6o869/production/cf18a4f4d06a9ed74ccc2bb4ee509bc30e1111e6-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=590 590w, https://cdn.sanity.io/images/nkt6o869/production/cf18a4f4d06a9ed74ccc2bb4ee509bc30e1111e6-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=884 884w, https://cdn.sanity.io/images/nkt6o869/production/cf18a4f4d06a9ed74ccc2bb4ee509bc30e1111e6-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1179 1179w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/cf18a4f4d06a9ed74ccc2bb4ee509bc30e1111e6-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1179&quot; width=&quot;1179&quot; height=&quot;2556&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of Sam&amp;#x27;s home screen&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/8d38c7c6240d53b6e1c68ee636303d62510f81de-1179x2556.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=295 295w, https://cdn.sanity.io/images/nkt6o869/production/8d38c7c6240d53b6e1c68ee636303d62510f81de-1179x2556.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=590 590w, https://cdn.sanity.io/images/nkt6o869/production/8d38c7c6240d53b6e1c68ee636303d62510f81de-1179x2556.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=884 884w, https://cdn.sanity.io/images/nkt6o869/production/8d38c7c6240d53b6e1c68ee636303d62510f81de-1179x2556.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1179 1179w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/8d38c7c6240d53b6e1c68ee636303d62510f81de-1179x2556.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1179&quot; width=&quot;1179&quot; height=&quot;2556&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;I’m bored out of my mind looking at these screenshots of my phone, but that’s by design. I’ve been trying to use my phone less throughout the day, so I’ve demoted (most) of my social apps to the app library…except Twitter. If I lose Twitter, I’d have to pick up a different vice to fill the void.&lt;/p&gt;&lt;p&gt;My home screen icons are mostly tools to do something, which I’ve found helps to trick my brain into viewing my phone as a utility rather than a recreational object. Widgets have been huge in this regard—I don’t even need to open apps to see what I want to know, and it dramatically reduces my screen time.&lt;/p&gt;&lt;p&gt;There’s not much to note for my lock screen. My brother is at school overseas, so I’ve got a world clock as a reminder to not text him at 2AM his time. I’ve been using &lt;a href=&quot;https://photos.google.com/share/AF1QipNi8VN2pw2Ya_xCV8eFgzEZmiXDy1-GwhXbqFtvXoH3HypF10as9puV8FdoVZpOZA/photo/AF1QipNOAJcK6dXWPZ7YvWuaLemvzJpw0ZIoTeANiyE9?key=WkZjQTIxQTM5a01oZkNUYTE2ZllKTVJKZk1CMTR3&quot;&gt;this original iPad wallpaper&lt;/a&gt; as my lock screen wallpaper since 2012 and it’s the objectively correct wallpaper. A bit of detail at the bottom, but nice and clear at the top for my widgets.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://twitter.com/mliberatore&quot;&gt;Michael Liberatore&lt;/a&gt;&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of Mike&amp;#x27;s watch face&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/7a7897f89182f6a112adc833b7f9d1db76c930b6-958x958.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=240 240w, https://cdn.sanity.io/images/nkt6o869/production/7a7897f89182f6a112adc833b7f9d1db76c930b6-958x958.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=479 479w, https://cdn.sanity.io/images/nkt6o869/production/7a7897f89182f6a112adc833b7f9d1db76c930b6-958x958.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=719 719w, https://cdn.sanity.io/images/nkt6o869/production/7a7897f89182f6a112adc833b7f9d1db76c930b6-958x958.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=958 958w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/7a7897f89182f6a112adc833b7f9d1db76c930b6-958x958.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=958&quot; width=&quot;958&quot; height=&quot;958&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Not much has changed in the &lt;a href=&quot;https://lickability.com/blog/the-many-watch-faces-of-lickability/#michael-liberatore&quot;&gt;last four years&lt;/a&gt; regarding my primary watch face. I’m still using the Modular (previously known as Infograph Modular) face, and most of my complications are the same. I’ve swapped out weather conditions for &lt;a href=&quot;https://www.nike.com/nrc-app&quot;&gt;Nike Run Club&lt;/a&gt;’s complication to quickly see my number of miles run in the current month. This gives me the extra nudge I need to keep my streaks on track. I’ve also switched from multicolor to orange, which unapologetically matches the garish accents on my new Apple Watch Ultra and its Orange Alpine Loop.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of Mike&amp;#x27;s lock screen&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/fbccf834d8bfba790e1771ace54a964325e95e85-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=295 295w, https://cdn.sanity.io/images/nkt6o869/production/fbccf834d8bfba790e1771ace54a964325e95e85-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=590 590w, https://cdn.sanity.io/images/nkt6o869/production/fbccf834d8bfba790e1771ace54a964325e95e85-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=884 884w, https://cdn.sanity.io/images/nkt6o869/production/fbccf834d8bfba790e1771ace54a964325e95e85-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1179 1179w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/fbccf834d8bfba790e1771ace54a964325e95e85-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1179&quot; width=&quot;1179&quot; height=&quot;2556&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;I’m still trying to figure out which widgets I want to live on my lock screen throughout the day, but for now, I’m pretty set on this setup during my sleep schedule. This collection of widgets gives me 99% of what I ever need when either going to bed or waking up. At night, I can quickly glance at my lock screen to see tomorrow’s first event using &lt;a href=&quot;https://flexibits.com/fantastical&quot;&gt;Fantastical&lt;/a&gt;’s “Up Next” widget. I can easily suppress my anxiety about whether my alarm is set thanks to the bonus alarm time that’s present on the lock screen during a sleep schedule, or even change the alarm if tomorrow’s first event is earlier. &lt;a href=&quot;https://overcast.fm/&quot;&gt;Overcast&lt;/a&gt;’s “Icon” widget allows me to quickly jump into the app to start a podcast without other distractions in the way (I fall asleep to podcasts). Finally, when initially waking up, the first thing I want to know is the temperature and conditions for my morning dog walk, which I can now see without unlocking my phone if I wake up before my alarm goes off and my morning summary appears. My background and text colors are a bit boring here, but that’s intentional for my &lt;em&gt;sleepability&lt;/em&gt;.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://twitter.com/mb&quot;&gt;mb Bischoff&lt;/a&gt;&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of mb&amp;#x27;s lock screen&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/fb24a4b0d4f328c1b7e02ad0e5d06e873bef1f48-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=295 295w, https://cdn.sanity.io/images/nkt6o869/production/fb24a4b0d4f328c1b7e02ad0e5d06e873bef1f48-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=590 590w, https://cdn.sanity.io/images/nkt6o869/production/fb24a4b0d4f328c1b7e02ad0e5d06e873bef1f48-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=884 884w, https://cdn.sanity.io/images/nkt6o869/production/fb24a4b0d4f328c1b7e02ad0e5d06e873bef1f48-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1179 1179w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/fb24a4b0d4f328c1b7e02ad0e5d06e873bef1f48-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1179&quot; width=&quot;1179&quot; height=&quot;2556&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of mb&amp;#x27;s home screen&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/5999c18361550180893ce5cdc8d96d62b291816c-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=295 295w, https://cdn.sanity.io/images/nkt6o869/production/5999c18361550180893ce5cdc8d96d62b291816c-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=590 590w, https://cdn.sanity.io/images/nkt6o869/production/5999c18361550180893ce5cdc8d96d62b291816c-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=884 884w, https://cdn.sanity.io/images/nkt6o869/production/5999c18361550180893ce5cdc8d96d62b291816c-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1179 1179w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/5999c18361550180893ce5cdc8d96d62b291816c-1179x2556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1179&quot; width=&quot;1179&quot; height=&quot;2556&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of mb&amp;#x27;s watch face&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e5874ce63e517f8f4065d59dbc82fe88e6570e5b-917x916.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=229 229w, https://cdn.sanity.io/images/nkt6o869/production/e5874ce63e517f8f4065d59dbc82fe88e6570e5b-917x916.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=459 459w, https://cdn.sanity.io/images/nkt6o869/production/e5874ce63e517f8f4065d59dbc82fe88e6570e5b-917x916.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=688 688w, https://cdn.sanity.io/images/nkt6o869/production/e5874ce63e517f8f4065d59dbc82fe88e6570e5b-917x916.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=917 917w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e5874ce63e517f8f4065d59dbc82fe88e6570e5b-917x916.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=917&quot; width=&quot;917&quot; height=&quot;916&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;My home screen and lock screen change with my eight different focus modes, but here’s what they look like during work. Artwork by one of my favorite illustrators, &lt;a href=&quot;https://dlanham.com/&quot;&gt;David Lanham&lt;/a&gt;, reminds me not to take things too seriously. Widgets from &lt;a href=&quot;https://flexibits.com/fantastical&quot;&gt;Fantastical&lt;/a&gt;, &lt;a href=&quot;https://streaksapp.com/&quot;&gt;Streaks&lt;/a&gt;, &lt;a href=&quot;https://www.omnigroup.com/omnifocus/&quot;&gt;OmniFocus&lt;/a&gt;, and &lt;a href=&quot;https://www.meetcarrot.com/weather/&quot;&gt;CARROT Weather&lt;/a&gt; show me the information I most often need at a glance. And I’ve got quick access to the apps I most frequently launch throughout the day to catch up on messages like Slack and Messages, jot down note with &lt;a href=&quot;https://getdrafts.com/&quot;&gt;Drafts&lt;/a&gt; and &lt;a href=&quot;https://logseq.com/&quot;&gt;Logseq&lt;/a&gt;, or take a break on Instagram and Twitter.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://twitter.com/jilliangmeehan&quot;&gt;Jillian Meehan&lt;/a&gt;&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of Jillian&amp;#x27;s lock screen&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/91a10b0b088825490764bdaf5b803b73a32561c4-1290x2796.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=323 323w, https://cdn.sanity.io/images/nkt6o869/production/91a10b0b088825490764bdaf5b803b73a32561c4-1290x2796.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=645 645w, https://cdn.sanity.io/images/nkt6o869/production/91a10b0b088825490764bdaf5b803b73a32561c4-1290x2796.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=968 968w, https://cdn.sanity.io/images/nkt6o869/production/91a10b0b088825490764bdaf5b803b73a32561c4-1290x2796.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1290 1290w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/91a10b0b088825490764bdaf5b803b73a32561c4-1290x2796.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1290&quot; width=&quot;1290&quot; height=&quot;2796&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of Jillian&amp;#x27;s home screen&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/7b7ec69748acfd4a7149b72f13a4f0bf44c0d3d0-1290x2796.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=323 323w, https://cdn.sanity.io/images/nkt6o869/production/7b7ec69748acfd4a7149b72f13a4f0bf44c0d3d0-1290x2796.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=645 645w, https://cdn.sanity.io/images/nkt6o869/production/7b7ec69748acfd4a7149b72f13a4f0bf44c0d3d0-1290x2796.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=968 968w, https://cdn.sanity.io/images/nkt6o869/production/7b7ec69748acfd4a7149b72f13a4f0bf44c0d3d0-1290x2796.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1290 1290w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/7b7ec69748acfd4a7149b72f13a4f0bf44c0d3d0-1290x2796.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1290&quot; width=&quot;1290&quot; height=&quot;2796&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;I also have a few different home screen + lock screen situations depending on what focus mode I‘m in, but this is what I see the majority of the time. On my lock screen, I’ve got widgets from &lt;a href=&quot;https://flexibits.com/fantastical&quot;&gt;Fantastical&lt;/a&gt;, &lt;a href=&quot;https://www.meetcarrot.com/weather/&quot;&gt;CARROT Weather&lt;/a&gt;, and &lt;a href=&quot;https://streaksapp.com/&quot;&gt;Streaks&lt;/a&gt;—plus the adorable pixel pal widget from &lt;a href=&quot;https://apolloapp.io/&quot;&gt;Apollo&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;My home screen has a few widget stacks working overtime—each stack has about five different widgets that I cycle through. Aside from the ones you see here (Fantastical, Spotify, and &lt;a href=&quot;https://www.omnigroup.com/omnifocus/&quot;&gt;OmniFocus&lt;/a&gt;) a few of my favorites are the &lt;a href=&quot;https://www.forestapp.cc/&quot;&gt;Forest&lt;/a&gt; time distribution widget, a small Streaks widget showing my next four tasks, &lt;a href=&quot;https://www.notion.so/&quot;&gt;Notion&lt;/a&gt; widgets linking to my most-used pages, and Pinterest widgets that show a random pin from a selected board (for the vibes). In my dock, I‘ve got Discord and Messages for quick communication, and a sort of all-in-one shortcut that I use to create new OmniFocus tasks or open frequently used apps.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of Jillian&amp;#x27;s watch face&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/bbc126cbc1e4327feb7df2bebe74137d3b322d3b-916x916.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=229 229w, https://cdn.sanity.io/images/nkt6o869/production/bbc126cbc1e4327feb7df2bebe74137d3b322d3b-916x916.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=458 458w, https://cdn.sanity.io/images/nkt6o869/production/bbc126cbc1e4327feb7df2bebe74137d3b322d3b-916x916.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=687 687w, https://cdn.sanity.io/images/nkt6o869/production/bbc126cbc1e4327feb7df2bebe74137d3b322d3b-916x916.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=916 916w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/bbc126cbc1e4327feb7df2bebe74137d3b322d3b-916x916.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=916&quot; width=&quot;916&quot; height=&quot;916&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;I have a pretty simple modular watch face—I can see the current weather from CARROT &amp;amp; my next event from Fantastical, and I have quick access to Spotify and the Home app.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://twitter.com/Twig777&quot;&gt;Andrew Harrison&lt;/a&gt;&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of Andrew&amp;#x27;s lock screen&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/37bebe35d10f88e268ecc15f0b735cb7d5126433-1170x2532.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=293 293w, https://cdn.sanity.io/images/nkt6o869/production/37bebe35d10f88e268ecc15f0b735cb7d5126433-1170x2532.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=585 585w, https://cdn.sanity.io/images/nkt6o869/production/37bebe35d10f88e268ecc15f0b735cb7d5126433-1170x2532.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=878 878w, https://cdn.sanity.io/images/nkt6o869/production/37bebe35d10f88e268ecc15f0b735cb7d5126433-1170x2532.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1170 1170w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/37bebe35d10f88e268ecc15f0b735cb7d5126433-1170x2532.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1170&quot; width=&quot;1170&quot; height=&quot;2532&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;This is my Lock Screen, but I’m not even on iOS 16 yet soooo...&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Need help building widgets for your app? &lt;a href=&quot;http://lickability.com/contact&quot;&gt;Let us know&lt;/a&gt;—maybe we can help.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Team Lickability</author></item><item><title>Our first retreat</title><link>https://lickability.com/blog/our-first-retreat/</link><guid isPermaLink="true">https://lickability.com/blog/our-first-retreat/</guid><description>Here&apos;s how it went</description><pubDate>Tue, 09 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Lickability has been fully remote for a while—we started (temporarily, we thought) working from home in 2020, which turned into us officially &lt;a href=&quot;/blog/closing-our-office&quot; title=&quot;Closing Our Office&quot; data-astro-cid-okzjwv6e&gt;shutting down our office&lt;/a&gt;  last September and making our first remote hire sometime later.&lt;/p&gt;&lt;p&gt;Being a remote company certainly has its benefits, but it also has tradeoffs—namely, the fact that we don’t get to see each other in person very often anymore. When we shut down the office, we knew we needed a plan to get everyone together a few times a year. A few months ago, we started planning our very first company retreat.&lt;/p&gt;&lt;h3&gt;Planning&lt;/h3&gt;&lt;p&gt;First, we had to figure out &lt;em&gt;where&lt;/em&gt; to host it. This was easy—most of the company is located in or near NYC, and we wanted to keep things relatively simple for our first time. After evaluating a few hotels in the area based on meeting space availability, price, and proximity to things in the area, we landed on the &lt;a href=&quot;https://www.wythehotel.com/&quot;&gt;Wythe Hotel&lt;/a&gt; as the site for our retreat.&lt;/p&gt;&lt;p&gt;Before we could book anything, we also had to decide &lt;em&gt;when&lt;/em&gt; and how long the retreat should be. We wanted to have enough time for it to feel meaningful without taking too much time and energy away from anyone. Two to three days felt like the right balance—an evening to check into the hotel and have dinner together, a full day for team activities, and a morning for people to check out of the hotel and spend a bit more time together before heading home.&lt;/p&gt;&lt;p&gt;Once the dates and location were locked down, all that was left to do was plan &lt;em&gt;what&lt;/em&gt; was going to happen when we actually got there. Corporate icebreakers have never really been our style, and we didn’t want the retreat to feel like work. But we also don’t have a lot of opportunities to get the whole team together in person, so we wanted to take full advantage of that.&lt;/p&gt;&lt;p&gt;In the end, we decided to use one day for work-related team activities—like a hackathon—and one day to get out of the hotel for some fun stuff. After throwing in a few dinner reservations and making sure all necessary travel was booked, we were ready to go.&lt;/p&gt;&lt;h3&gt;How it went&lt;/h3&gt;&lt;p&gt;After months of planning, it was cool to see everything finally come together as people started checking into the hotel Wednesday night. After everyone got settled, dinner at the hotel restaurant was the first thing on our agenda.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/ff58afc41680d0872e7a7135bd349c3e2aa9304f-3232x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/ff58afc41680d0872e7a7135bd349c3e2aa9304f-3232x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/ff58afc41680d0872e7a7135bd349c3e2aa9304f-3232x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/ff58afc41680d0872e7a7135bd349c3e2aa9304f-3232x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/ff58afc41680d0872e7a7135bd349c3e2aa9304f-3232x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/ff58afc41680d0872e7a7135bd349c3e2aa9304f-3232x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/ff58afc41680d0872e7a7135bd349c3e2aa9304f-3232x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/ff58afc41680d0872e7a7135bd349c3e2aa9304f-3232x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/ff58afc41680d0872e7a7135bd349c3e2aa9304f-3232x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;760&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt; Some of us met up in one of our hotel rooms before dinner!&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;On Thursday, we booked the Cooper Room at the Wythe for the full day—it’s a cozy cellar space with plenty of room for team activities over brunch (which was delicious, by the way).&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6f226e477bf592ad12387e24e13e2983c25737f8-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/6f226e477bf592ad12387e24e13e2983c25737f8-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/6f226e477bf592ad12387e24e13e2983c25737f8-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/6f226e477bf592ad12387e24e13e2983c25737f8-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/6f226e477bf592ad12387e24e13e2983c25737f8-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/6f226e477bf592ad12387e24e13e2983c25737f8-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/6f226e477bf592ad12387e24e13e2983c25737f8-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/6f226e477bf592ad12387e24e13e2983c25737f8-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6f226e477bf592ad12387e24e13e2983c25737f8-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Our brunch buffet&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;After a great &lt;a href=&quot;https://lickability.com/blog/learning-with-lickability/&quot;&gt;Learn with Lickability&lt;/a&gt; presentation from Brian, Ken, and Michael Liberatore in the morning, the engineers split into groups for a hackathon while the rest of us did some brainstorming for future product ideas. These are the kinds of activities that work best if you can get everyone together in person—having the ability to work side-by-side and bounce ideas off of each other is fun, and definitely helps facilitate creativity.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/53143898756904515f57c5efa5bce2d79bafcade-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/53143898756904515f57c5efa5bce2d79bafcade-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/53143898756904515f57c5efa5bce2d79bafcade-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/53143898756904515f57c5efa5bce2d79bafcade-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/53143898756904515f57c5efa5bce2d79bafcade-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/53143898756904515f57c5efa5bce2d79bafcade-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/53143898756904515f57c5efa5bce2d79bafcade-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/53143898756904515f57c5efa5bce2d79bafcade-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/53143898756904515f57c5efa5bce2d79bafcade-4032x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/fe29a4413096889f48283b878c5089437316a4c2-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/fe29a4413096889f48283b878c5089437316a4c2-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/fe29a4413096889f48283b878c5089437316a4c2-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/fe29a4413096889f48283b878c5089437316a4c2-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/fe29a4413096889f48283b878c5089437316a4c2-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/fe29a4413096889f48283b878c5089437316a4c2-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2049 2049w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/fe29a4413096889f48283b878c5089437316a4c2-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1199&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;A very fun &amp;amp; busy day&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;The last day of the retreat, Friday, was our “fun stuff” day. After checking out of the hotel in the morning, we met up at &lt;a href=&quot;https://www.sipnplaynyc.com/&quot;&gt;Sip &amp;amp; Play&lt;/a&gt;, a board game cafe. Many rounds of games later, about half the team got together for our last event: &lt;a href=&quot;https://beatthebomb.com/&quot;&gt;Beat the Bomb&lt;/a&gt;, a very competitive, fun, and messy escape room.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Three Lickability employees wearing white hazmat suits covered in paint, posing to celebrate their win&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d55fcd686704cdc060f570fc8dfb9d2e095ec5f0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/d55fcd686704cdc060f570fc8dfb9d2e095ec5f0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/d55fcd686704cdc060f570fc8dfb9d2e095ec5f0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/d55fcd686704cdc060f570fc8dfb9d2e095ec5f0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/d55fcd686704cdc060f570fc8dfb9d2e095ec5f0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/d55fcd686704cdc060f570fc8dfb9d2e095ec5f0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/d55fcd686704cdc060f570fc8dfb9d2e095ec5f0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/d55fcd686704cdc060f570fc8dfb9d2e095ec5f0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d55fcd686704cdc060f570fc8dfb9d2e095ec5f0-6000x4000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1067&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/bd520f7ea04f762c3d8f9b30283127d3d14b332c-2305x1537.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/bd520f7ea04f762c3d8f9b30283127d3d14b332c-2305x1537.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/bd520f7ea04f762c3d8f9b30283127d3d14b332c-2305x1537.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/bd520f7ea04f762c3d8f9b30283127d3d14b332c-2305x1537.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/bd520f7ea04f762c3d8f9b30283127d3d14b332c-2305x1537.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/bd520f7ea04f762c3d8f9b30283127d3d14b332c-2305x1537.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2305 2305w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/bd520f7ea04f762c3d8f9b30283127d3d14b332c-2305x1537.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1067&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;The winners vs. the losers&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;What we learned&lt;/h3&gt;&lt;p&gt;I think it’s safe to say the first Lickability retreat was a success! It was great to get everyone together in person after being remote for two years—and if the post-retreat survey we sent out is any indication, everyone seemed to have a fun time.&lt;/p&gt;&lt;p&gt;Now that we’ve been through it once, it’ll be a bit easier to plan the next one. We’ll have a better sense of what types of activities people like, what the schedule should look like, and where we could have been a little more efficient.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/0c7d8e695a2aef9c145651ba78ddaf3bfb205f77-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/0c7d8e695a2aef9c145651ba78ddaf3bfb205f77-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/0c7d8e695a2aef9c145651ba78ddaf3bfb205f77-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/0c7d8e695a2aef9c145651ba78ddaf3bfb205f77-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/0c7d8e695a2aef9c145651ba78ddaf3bfb205f77-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/0c7d8e695a2aef9c145651ba78ddaf3bfb205f77-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2049 2049w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/0c7d8e695a2aef9c145651ba78ddaf3bfb205f77-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1199&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d558ac640cafcf9a46265704c2f385f103668fb8-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/d558ac640cafcf9a46265704c2f385f103668fb8-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/d558ac640cafcf9a46265704c2f385f103668fb8-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/d558ac640cafcf9a46265704c2f385f103668fb8-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/d558ac640cafcf9a46265704c2f385f103668fb8-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/d558ac640cafcf9a46265704c2f385f103668fb8-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2049 2049w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d558ac640cafcf9a46265704c2f385f103668fb8-2049x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1199&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Our morning at Sip &amp;amp; Play&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;On the other hand, there’s still lots to experiment with: we need to figure out how often we want to have retreats, how long they should be, and where we should hold them. We don’t want to do the same thing every time, but we also don’t want to take huge risks—there’s always a balance.&lt;/p&gt;&lt;p&gt;I think I can speak for the rest of the company when I say I’m really looking forward to our next company retreat, whatever it ends up looking like. It can sometimes be rough not getting to see your coworkers very often, but having an excuse to plan a few days of fun together definitely makes up for it. Here’s hoping there are many more retreats in our future!&lt;/p&gt;&lt;p&gt;(As for making the switch to working remotely, that’s still going really well—and we’ll talk more about it in a future blog post. 😉)&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Wanna know what else we‘re up to? Give us a follow on &lt;a href=&quot;http://twitter.com/lickability&quot;&gt;Twitter&lt;/a&gt;.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Jillian Meehan</author></item><item><title>Conferences Condensed: WWDC22</title><link>https://lickability.com/blog/conferences-condensed-wwdc22/</link><guid isPermaLink="true">https://lickability.com/blog/conferences-condensed-wwdc22/</guid><description>5 of our favorite sessions</description><pubDate>Thu, 16 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It‘s that time of year again—we always look forward to a week full of design and development talks from Apple. This time around, a few of us were able to meet up in person to watch the Keynote together, in addition to streaming and chatting about sessions in Discord like we usually do. Out of all the videos our team watched this week, here are a few that stood out as our favorites.&lt;/p&gt;&lt;h3&gt;📊 &lt;a href=&quot;https://developer.apple.com/wwdc22/110342&quot;&gt;Design app experiences with Charts&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;I’ve been a fan of &lt;a href=&quot;http://feltron.com/&quot;&gt;Nicholas Felton&lt;/a&gt; for over a decade, and I mostly knew him as the creator of the beautiful Feltron Annual Reports. So it was a pleasant surprise to me that he now works at Apple as a designer primarily focused on data visualization with charts. In this talk, Nicholas walks through when developers should use charts in their UIs and gives a bunch of useful tips on how to progressively disclose and explore data with both static and interactive charts built in the new Swift Charts framework. I imagine I’ll be returning to this talk many times as I start to think through more charts in apps like &lt;a href=&quot;https://buildwatch.app/&quot;&gt;Buildwatch&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;em&gt;– mb Bischoff&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;🎨 &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2022/10056/&quot;&gt;Compose custom layouts with SwiftUI&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;This session introduced fantastic new layout capabilities that bring &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Layout&lt;/code&gt;-like flexibility and transitions to SwiftUI, and fill in some gaps for more common layouts that were more difficult to achieve with the lack of layout constraints.&lt;/p&gt;&lt;p&gt;While the entire session was fantastic, what stood out most to me was &lt;a href=&quot;https://developer.apple.com/documentation/SwiftUI/ViewThatFits&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;View​That​Fits&lt;/code&gt;&lt;/a&gt;. This new API was touched on during &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2022/102/?time=1585&quot;&gt;Platforms State of the Union&lt;/a&gt;, but in this more detailed session, we see its use in context with more of the new layout features. It doesn’t get a lot of screen time, but it still proved to be my most 🤯 moment this year. It’s very often that a designer will ask us to make adjustments like “if we run out of horizontal space, just remove this purely decorative component, or this supplementary label”. Seems simple enough, but it’s not—until now! Without using complicated layout math, lots of text measurement, or relying solely on the very broad size classes as layout breakpoints, we can now swap between layouts only when we need to, and the API for it couldn’t be simpler.&lt;/p&gt;&lt;p&gt;&lt;em&gt;– Michael Liberatore&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;🗺 &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2022/10001/&quot;&gt;Explore navigation design for iOS&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;I love all the design talks that happen at WWDC every year. This year, my favorite talk was (again) about design—this time focused on navigation design for iOS. Navigation design encompasses how things behave, where things belong, and how things work. And good navigation, as this talk highlights, is often unnoticed so the user’s focus is on the content of the app.&lt;/p&gt;&lt;p&gt;The talk went over best practices for using tab bars, hierarchical navigation, and modal presentations. &lt;strong&gt;Tab bars&lt;/strong&gt; should reflect your information hierarchy, and can tell a story about what the app can do, even without the content of the app. &lt;strong&gt;Hierarchical navigation&lt;/strong&gt; is used to push users between pages of content that becomes more specific as you move down the hierarchy. And &lt;strong&gt;modal presentations&lt;/strong&gt; are useful for focused, self-contained tasks. Watch the talk to learn more about the best practices for using each!&lt;/p&gt;&lt;p&gt;&lt;em&gt;– Thomas DeVuono&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;🖥 &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2022/110360/&quot;&gt;Use Xcode for server-side development&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Swift, but on the server! Seeing Apple use Vapor, a community-built server-side Swift framework, in a WWDC session is very exciting! I’ve done some Vapor development on internal projects and seeing Apple recommend it lets us know that it’s here to stay. This introduction session shows how a server project can be in the same Xcode project as the iOS code, and how it can be deployed to Heroku. The Vapor docs give more in-depth instructions, but this session is a great introduction to what’s possible.&lt;/p&gt;&lt;p&gt;&lt;em&gt;– Mikaela Caron&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;✍️ &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2022/10037/&quot;&gt;Writing for interfaces&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;When you know their power, words are a force to be reckoned with. In this talk, &lt;a href=&quot;https://twitter.com/kaelycoon&quot;&gt;Kaely Coon&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/wantapeanut&quot;&gt;Jennifer Bush&lt;/a&gt; cover how to use them effectively in your app through the PACE framework:&lt;/p&gt;&lt;h4&gt;Purpose&lt;/h4&gt;&lt;p&gt;Consider the purpose of the screen you’re writing—what should someone know at that moment?&lt;/p&gt;&lt;h4&gt;Anticipation&lt;/h4&gt;&lt;p&gt;It helps to write copy as if you’re having a conversation with the person on the other side of the screen. A conversation has an organic flow where participants vary their tone and can anticipate what’s next.&lt;/p&gt;&lt;h4&gt;Context&lt;/h4&gt;&lt;p&gt;Context and timing is a huge part of writing effective copy. For a navigation app, for example, the person will (hopefully) not be directing their attention toward your app. Write time-sensitive copy that’s short &lt;a href=&quot;https://www.youtube.com/watch?v=VvPaEsuz-tY&quot;&gt;without sounding terse&lt;/a&gt;.&lt;/p&gt;&lt;h4&gt;Empathy&lt;/h4&gt;&lt;p&gt;Write for everybody, regardless of region, language, or how someone interacts with their device. If your app targets a defined audience, speak to that audience without leaving anyone out.&lt;/p&gt;&lt;p&gt;Designers and front-end developers, take note. These lessons, while practical, extend far beyond iOS or even UX writing. They can be applied to web apps, emails, or &lt;a href=&quot;https://youtu.be/bH1xAbhlJSo?t=445&quot;&gt;interoffice signs on how to use a frustratingly-designed elevator&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;– Sam Henri Gold&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;If you liked these talks &amp;amp; want more iOS dev tips, give us a follow on &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;Twitter&lt;/a&gt;!&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Team Lickability</author></item><item><title>Conferences Condensed: 360iDev</title><link>https://lickability.com/blog/conferences-condensed-360idev/</link><guid isPermaLink="true">https://lickability.com/blog/conferences-condensed-360idev/</guid><description>A few of my favorite sessions</description><pubDate>Thu, 02 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last August, I got the chance to attend 360iDev, a long running iOS and macOS conference based in Denver, Colorado. After a two year hiatus from attending in-person conferences, I was excited to see a conference in action again. Since WWDC is right around the corner, here’s a refresher on some SwiftUI focused sessions—enjoy!&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://vimeo.com/594905113&quot;&gt;SwiftUI.View: Codable – Transforming SwiftUI to JSON for a fully backend driven UI&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://twitter.com/CaffeineFlo&quot;&gt;&lt;strong&gt;Florian Harr&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Florian starts by explaining the flexibility of SwiftUI static views via parameterization and dynamic screens with shipped views (also known as as optional or conditional views). Since SwiftUI views are structs they can automatically synthesize &lt;a href=&quot;https://developer.apple.com/documentation/swift/codable&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Codable&lt;/code&gt;&lt;/a&gt; features for encoding and decoding JSON as long as the underlying properties also conform to Codable. Florian provides a neat demo for this which can be found &lt;a href=&quot;https://github.com/caffeineflo/360iDev2021&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://360idev.com/session-videos/?vimeography_gallery=11&amp;vimeography_video=596081705&quot;&gt;Async Await and more in Swift&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://twitter.com/camdeardorff&quot;&gt;&lt;strong&gt;Cameron Deardorff&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Cameron starts off with an example of error prone completion handlers, the complexity for scale, and the inevitable &lt;a href=&quot;https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming)&quot;&gt;pyramid of doom&lt;/a&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/1de6285fb3a302e9e7bc9f7cd9c4d678d3923f10-1089x695.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=272 272w, https://cdn.sanity.io/images/nkt6o869/production/1de6285fb3a302e9e7bc9f7cd9c4d678d3923f10-1089x695.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=545 545w, https://cdn.sanity.io/images/nkt6o869/production/1de6285fb3a302e9e7bc9f7cd9c4d678d3923f10-1089x695.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=817 817w, https://cdn.sanity.io/images/nkt6o869/production/1de6285fb3a302e9e7bc9f7cd9c4d678d3923f10-1089x695.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1089 1089w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/1de6285fb3a302e9e7bc9f7cd9c4d678d3923f10-1089x695.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1089&quot; width=&quot;1089&quot; height=&quot;695&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;This talk goes over the familiar paradigm of async/await around error handling with throwing functions. Async/await isn’t limited to functions—they can be used with property getters and initializers. This talk also goes over the ergonomics of structured concurrency around &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;async let&lt;/code&gt; and the ability to run in parallel with &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Task&lt;/code&gt; along with the hierarchy of &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Task&lt;/code&gt;. Fun fact: you can use a &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;Task.Handle&lt;/code&gt; as a replacement to storing Combine’s &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;Any​Cancellable&lt;/code&gt; to cancel a task and avoid any unnecessary background processing. Cameron also dives into shared mutable state with actors. This talk goes in depth around different use cases where you might want to use these features.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://360idev.com/session-videos/?vimeography_gallery=11&amp;vimeography_video=594936040&quot;&gt;Multi-platform Development, the SwiftUI Way&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://twitter.com/malinsundberg&quot;&gt;&lt;strong&gt;Malin Sundberg&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Malin gives a delightful talk with a tour of Vancouver, Canada! Malin goes over the intricacies of developing a multi platform app for time tracking and invoicing, &lt;a href=&quot;https://apps.apple.com/us/app/orbit-time-based-invoicing/id1501298198&quot;&gt;Orbit&lt;/a&gt;. This talk goes over the expectations of writing a SwiftUI app and the necessity to split views into components for reusability along with legacy code use cases. It also includes compiler directive examples within view modifiers and efficient ways to use them. Malin emphasizes the point that SwiftUI isn’t a &lt;em&gt;write once, run everywhere&lt;/em&gt; solution but more of a &lt;em&gt;learn once and apply to different platforms&lt;/em&gt; framework. This talk also goes over how to leverage UIKit within SwiftUI via protocols like &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/uiviewrepresentable&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;UI​View​Representable&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;UI​View​Controller​Representable&lt;/code&gt;&lt;/a&gt; for more complex layouts, and using coordinators for communicating changes and handling interactions like transitions in a page view controller.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://360idev.com/session-videos/?vimeography_gallery=11&amp;vimeography_video=601977623&quot;&gt;Navigating DocC&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://twitter.com/designatednerd&quot;&gt;&lt;strong&gt;Ellen Shapiro&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Ellen starts off with what C stands for in DocC, which is Compiler! Since the documentation compiler is tightly integrated with the swift compiler. At Lickability we document &lt;em&gt;everything&lt;/em&gt;, so I was super excited to try this since Ellen mentioned how simple it is to use existing swift docs to generate a DocC version via the Xcode menu &lt;strong&gt;Product → Build Documentation&lt;/strong&gt;. That will generate docs that look exactly like Apple’s via their documentation viewer:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/daf65c22dded93d62ac24c3f408f8500833101e9-2988x1736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/daf65c22dded93d62ac24c3f408f8500833101e9-2988x1736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/daf65c22dded93d62ac24c3f408f8500833101e9-2988x1736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/daf65c22dded93d62ac24c3f408f8500833101e9-2988x1736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/daf65c22dded93d62ac24c3f408f8500833101e9-2988x1736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/daf65c22dded93d62ac24c3f408f8500833101e9-2988x1736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/daf65c22dded93d62ac24c3f408f8500833101e9-2988x1736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/daf65c22dded93d62ac24c3f408f8500833101e9-2988x1736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2988 2988w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/daf65c22dded93d62ac24c3f408f8500833101e9-2988x1736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;930&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Fancy! This talk also goes over how to add links to your types via a double back tick, e.g. &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Avatar​Image​View&lt;/code&gt;. That will also trigger autocomplete since the swift compiler is used. DocC also accounts for typos in case types are misspelled which would trigger a warning. A caveat in symbol linking is that you can‘t link to anything outside your library’s context. Linking for specific enum cases isn‘t supported yet either. Ellen goes over the extensive limitations in DocC, including linking within related frameworks or even the standard library. Any public extensions of types that are not initially declared in your framework will not have documentation generated, which can be a pain point if you use extensions heavily. Ellen goes over how to view a DocC archive file by looking at the package contents, and explains that under the hood this is running as a Vue.Js instance—interesting! Since this conference talk, Apple has open sourced &lt;a href=&quot;https://www.swift.org/blog/swift-docc/&quot;&gt;DocC&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Summary&lt;/h3&gt;&lt;p&gt;Since this conference, we‘ve been using a handful of these SwiftUI features at Lickability. It’s always exciting to see talks like these using SwiftUI in production or in testing!&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Watch these and other sessions from 360iDev on their &lt;a href=&quot;https://360idev.com/session-videos/&quot;&gt;website&lt;/a&gt;.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Daisy Ramos</author></item><item><title>Conferences Condensed: Extreme Ownership Muster</title><link>https://lickability.com/blog/extreme-ownership-muster/</link><guid isPermaLink="true">https://lickability.com/blog/extreme-ownership-muster/</guid><description>A conference about leadership</description><pubDate>Tue, 03 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last October, I went to Las Vegas to attend &lt;a href=&quot;https://events.echelonfront.com/muster/&quot;&gt;Extreme Ownership Muster&lt;/a&gt;, a leadership conference by &lt;a href=&quot;https://events.echelonfront.com/muster/&quot;&gt;Echelon Front&lt;/a&gt;. The center of this conference is a book called Extreme Ownership, written by Jocko Willink and Leif Babin. The book shows scenarios Willink or Babin faced as Navy SEALs, the leadership principles that come out of those examples, and then how those same principles can be applied to business—the conference followed a similar format during each session.&lt;/p&gt;&lt;h3&gt;⚔️ Laws of Combat&lt;/h3&gt;&lt;p&gt;Speakers: &lt;a href=&quot;https://twitter.com/jockowillink&quot;&gt;Jocko Willink&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/leifbabin&quot;&gt;Leif Babin&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/JPDinnell&quot;&gt;JP Dinnell&lt;/a&gt;, and &lt;a href=&quot;https://twitter.com/davidrberke&quot;&gt;Dave Berke&lt;/a&gt;&lt;/p&gt;&lt;p&gt;The conference kicked off with the Laws of Combat. These are the principles to being a leader—they build off of each other and work together to accomplish the mission. Those laws are:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Cover and Move&lt;/strong&gt; — It’s all about the team. If the team fails, everyone fails.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Simple&lt;/strong&gt; — Everything must be simple. The objective, instructions, everything. It’s up to the leader to ensure the team understands the task.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Prioritize and Execute&lt;/strong&gt; — Prioritize tasks and do them. Debrief after everything, and figure out how do it better the next time.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Decentralized Command&lt;/strong&gt; — Give your team the power to make decisions so you can focus on the big picture. The team should not only understand what to do but &lt;em&gt;why&lt;/em&gt; they’re doing it. The leader is able to detach and focus on the strategic objective.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The Laws of Combat are to be followed in order, and after hearing that, it made sense to me. You must first be a team, cover and move, before you can have decentralized command where the team can autonomously execute tasks without needing explicit approval for every step. I think The Laws of Combat are a great starting point for any leader.&lt;/p&gt;&lt;h3&gt;🧮 Strategic vs Tactical Thinking&lt;/h3&gt;&lt;p&gt;Speakers: &lt;a href=&quot;https://twitter.com/jockowillink&quot;&gt;Jocko Willink&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/leifbabin&quot;&gt;Leif Babin&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Next, we covered two different ways of thinking. &lt;strong&gt;Strategic&lt;/strong&gt; thinking is all about long term goals, and &lt;strong&gt;tactical&lt;/strong&gt; thinking is about short term goals. You can be making continuous tactical wins, but if they don’t help the strategic objective, they‘re useless. Take a step back and recognize, analyze, and react. You don’t want to have a tactical victory that comes with a strategic failure.&lt;/p&gt;&lt;p&gt;As a leader, you must think both strategically and tactically. I’ve never thought about this before—you can make tactical wins, but when those don’t help you strategically, they won’t make a difference and you need to pivot. Detaching yourself from the situation and observing is a superpower. If you’re too close, you may miss the big picture.&lt;/p&gt;&lt;h3&gt;🔍 Decision Making &amp;amp; the OODA Loop&lt;/h3&gt;&lt;p&gt;Speakers: &lt;a href=&quot;https://twitter.com/davidrberke&quot;&gt;Dave Berke&lt;/a&gt;&lt;/p&gt;&lt;p&gt;In this talk, we covered the OODA Loop—a method created by &lt;a href=&quot;https://en.wikipedia.org/wiki/John_Boyd_(military_strategist)&quot;&gt;John Boyd&lt;/a&gt; for taking in information, processing it, and using it to make a decision. This method has four steps:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Observe&lt;/strong&gt; — Gather information from various different places: your customers, your competition, your team, etc.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Orient&lt;/strong&gt; — Put that information into context and turn your raw data into useful information. Your past experiences can help you predict the future. This is Boyd’s most critical part of the loop.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Decide&lt;/strong&gt; — This is where you start to take extreme ownership. You shift away from the science of the loop and start leading.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Act&lt;/strong&gt; — This is the most important step: implementing your plan.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;This loop repeats, and observations or feedback from previous plans and actions are used to reorient yourself and make new decisions and actions.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Mikaela with Jocko Willink and Leif Babin&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/7c45960bee2a9826a854d6688a71b02256637c7e-4032x3024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/7c45960bee2a9826a854d6688a71b02256637c7e-4032x3024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/7c45960bee2a9826a854d6688a71b02256637c7e-4032x3024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/7c45960bee2a9826a854d6688a71b02256637c7e-4032x3024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/7c45960bee2a9826a854d6688a71b02256637c7e-4032x3024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/7c45960bee2a9826a854d6688a71b02256637c7e-4032x3024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/7c45960bee2a9826a854d6688a71b02256637c7e-4032x3024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/7c45960bee2a9826a854d6688a71b02256637c7e-4032x3024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/7c45960bee2a9826a854d6688a71b02256637c7e-4032x3024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Me meeting Jocko Willink and Leif Babin, the authors of Extreme Ownership and founders of Echelon Front.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Extreme Ownership Muster covered some interesting methods for approaching leadership, both in life and in business. If you‘re interested in learning more, you can check out the &lt;a href=&quot;https://echelonfront.com/extreme-ownership/&quot;&gt;books&lt;/a&gt; written by the Echelon Front team that put together the conference!&lt;/p&gt; </content:encoded><author>Mikaela Caron</author></item><item><title>Every Screen in Your App Should Be a Scrolling View</title><link>https://lickability.com/blog/every-screen-in-your-app-should-be-a-scrolling-view/</link><guid isPermaLink="true">https://lickability.com/blog/every-screen-in-your-app-should-be-a-scrolling-view/</guid><description>Yes, really</description><pubDate>Wed, 23 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this blog post, I’m going to tell you why every major screen in your application should be embedded in a scrolling view.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Wait, what? Not everything in my application needs to scroll!&lt;/em&gt; I used to agree with you—but I’ve learned over the past five years that that‘s almost &lt;em&gt;always&lt;/em&gt; the wrong assumption.&lt;/p&gt;&lt;p&gt;I’m going to highlight why almost every screen in your app should be embedded in something like &lt;strong&gt;UIKit’s&lt;/strong&gt;&lt;code index=&quot;2&quot; isInline=&quot;true&quot;&gt;UI​Scroll​View&lt;/code&gt;, &lt;code index=&quot;4&quot; isInline=&quot;true&quot;&gt;UI​Table​View&lt;/code&gt;, or &lt;code index=&quot;6&quot; isInline=&quot;true&quot;&gt;UI​Collection​View&lt;/code&gt;; or &lt;strong&gt;SwiftUI’s&lt;/strong&gt;&lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;Scroll​View&lt;/code&gt;, &lt;code index=&quot;11&quot; isInline=&quot;true&quot;&gt;List&lt;/code&gt;, or &lt;code index=&quot;13&quot; isInline=&quot;true&quot;&gt;Form&lt;/code&gt; .&lt;/p&gt;&lt;h3&gt;What is a “screen”?&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Screens&lt;/strong&gt; are what a user sees on their device. They are high level containers that contain various UI elements related to a specific task. In an app like Slack, each of the tabs on the tab bar is a screen—as well as each channel you open.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f910f9afe9a63de847913febe9f244c13f0de29d-2250x2576.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/f910f9afe9a63de847913febe9f244c13f0de29d-2250x2576.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/f910f9afe9a63de847913febe9f244c13f0de29d-2250x2576.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/f910f9afe9a63de847913febe9f244c13f0de29d-2250x2576.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/f910f9afe9a63de847913febe9f244c13f0de29d-2250x2576.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/f910f9afe9a63de847913febe9f244c13f0de29d-2250x2576.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2250 2250w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f910f9afe9a63de847913febe9f244c13f0de29d-2250x2576.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1832&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;To oversimplify it from a code standpoint, most &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​View​Controller&lt;/code&gt;s represent the screens of your app. In SwiftUI, it’s harder to draw a destination. &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;View&lt;/code&gt;s can be a container for the whole screen while also being made up of smaller &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;View&lt;/code&gt;s, like a &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;Login​View&lt;/code&gt; having a &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;Text​Field&lt;/code&gt; view.&lt;/p&gt;&lt;h3&gt;What are some parts of an app that may not scroll?&lt;/h3&gt;&lt;p&gt;I’ve found that most developers don’t naturally set up screens like onboarding, login, and signup to scroll. I think this is mainly because these screens tend to have pretty simple UI with minimal content.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/ea91e6dda50e92fc769330a0e95821c232df9af3-500x1083.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=250 250w, https://cdn.sanity.io/images/nkt6o869/production/ea91e6dda50e92fc769330a0e95821c232df9af3-500x1083.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=500 500w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/ea91e6dda50e92fc769330a0e95821c232df9af3-500x1083.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=500&quot; width=&quot;500&quot; height=&quot;1083&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Now, let’s look at three main reasons why these screens &lt;em&gt;should&lt;/em&gt; scroll—even though they may not look like they need to.&lt;/p&gt;&lt;h4&gt;Keyboard&lt;/h4&gt;&lt;p&gt;In the case of screens like login and signup, you’ll often have to deal with the keyboard being on screen. The user should be able to interact with the content while having the keyboard up. Being in a scroll view allows you to adjust the content inset or have it automatically handled depending on what implementation you picked.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/52e931f310c0122a2caadf405977a2434747a0ae-943x1070.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=236 236w, https://cdn.sanity.io/images/nkt6o869/production/52e931f310c0122a2caadf405977a2434747a0ae-943x1070.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=472 472w, https://cdn.sanity.io/images/nkt6o869/production/52e931f310c0122a2caadf405977a2434747a0ae-943x1070.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=707 707w, https://cdn.sanity.io/images/nkt6o869/production/52e931f310c0122a2caadf405977a2434747a0ae-943x1070.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=943 943w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/52e931f310c0122a2caadf405977a2434747a0ae-943x1070.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=943&quot; width=&quot;943&quot; height=&quot;1070&quot;/&gt;  &lt;/figure&gt; &lt;h4&gt;Device Size&lt;/h4&gt;&lt;p&gt;As Apple continues to introduce new device designs with various screen sizes, it’s hard to design a screen that fits and looks good on both an iPhone 13 Pro Max and an iPhone 13 Mini. Being in a scroll view gives you the flexibility to keep your preferred design for the larger devices, and instead of making a custom one for smaller devices, it can scroll when it needs to.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/8a5850b22e20fb3279b526e42b14231ce9662cf0-936x1063.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=234 234w, https://cdn.sanity.io/images/nkt6o869/production/8a5850b22e20fb3279b526e42b14231ce9662cf0-936x1063.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=468 468w, https://cdn.sanity.io/images/nkt6o869/production/8a5850b22e20fb3279b526e42b14231ce9662cf0-936x1063.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=702 702w, https://cdn.sanity.io/images/nkt6o869/production/8a5850b22e20fb3279b526e42b14231ce9662cf0-936x1063.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=936 936w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/8a5850b22e20fb3279b526e42b14231ce9662cf0-936x1063.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=936&quot; width=&quot;936&quot; height=&quot;1063&quot;/&gt;  &lt;/figure&gt; &lt;h4&gt;Dynamic Type&lt;/h4&gt;&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/accessibility/overview/introduction/&quot;&gt;Accessibility shouldn’t be optional&lt;/a&gt;, so your app should support Dynamic Type throughout the interface. &lt;em&gt;Woah there&lt;/em&gt;, that sounds like a lot of work!&lt;/p&gt;&lt;p&gt;If you architect all your screens to be in scrolling views, a majority of the work to support Dynamic Type is already done. As the user adjusts the type size to fit their needs, the view can shrink and grow, allowing itself to scroll if you run out of space.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/09ae8571f440f564a0d19bd7f40b5c442849483f-941x1073.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=235 235w, https://cdn.sanity.io/images/nkt6o869/production/09ae8571f440f564a0d19bd7f40b5c442849483f-941x1073.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=471 471w, https://cdn.sanity.io/images/nkt6o869/production/09ae8571f440f564a0d19bd7f40b5c442849483f-941x1073.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=706 706w, https://cdn.sanity.io/images/nkt6o869/production/09ae8571f440f564a0d19bd7f40b5c442849483f-941x1073.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=941 941w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/09ae8571f440f564a0d19bd7f40b5c442849483f-941x1073.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=941&quot; width=&quot;941&quot; height=&quot;1073&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Example&lt;/h3&gt;&lt;p&gt;Now that you know why a simple screen should scroll, I’ll walk you through an example of taking a basic sign up screen and allowing it to scroll if needed.&lt;/p&gt;&lt;p&gt;Here, you have a view for signing up. There are text fields for email, password, and password confirmation. There is also a sign up button.&lt;/p&gt;&lt;p&gt;At first glance, you might consider building this with a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Stack​View&lt;/code&gt; that has a couple &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;UI​Text​Field&lt;/code&gt;s and a &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;UI​Button&lt;/code&gt;. Alternatively, in SwiftUI, you may build it with a &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;V​Stack&lt;/code&gt; that includes a few &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;Text​Field&lt;/code&gt;s and a &lt;code index=&quot;11&quot; isInline=&quot;true&quot;&gt;Button&lt;/code&gt;. Here’s what it looks like on an iPhone 13 Pro Max vs. iPhone 13 Mini with an increased Dynamic Text size:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/32bdc15961fbbf194097921466be8ae6c922e394-944x1067.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=236 236w, https://cdn.sanity.io/images/nkt6o869/production/32bdc15961fbbf194097921466be8ae6c922e394-944x1067.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=472 472w, https://cdn.sanity.io/images/nkt6o869/production/32bdc15961fbbf194097921466be8ae6c922e394-944x1067.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=708 708w, https://cdn.sanity.io/images/nkt6o869/production/32bdc15961fbbf194097921466be8ae6c922e394-944x1067.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=944 944w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/32bdc15961fbbf194097921466be8ae6c922e394-944x1067.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=944&quot; width=&quot;944&quot; height=&quot;1067&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;We’ve got a problem!&lt;/p&gt;&lt;h4&gt;UIKit&lt;/h4&gt;&lt;p&gt;You could convert this view over to using &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Table​View&lt;/code&gt; or &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;UI​Collection​View&lt;/code&gt; but the content isn’t really being reused, which is one of the main benefits of those classes. I’d like it to scroll, but I don’t need all those optimizations. That’s where you can use good old &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;UI​Scroll​View&lt;/code&gt;. You can embed your &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;UI​Stack​View&lt;/code&gt; in a &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;UI​Scroll​View&lt;/code&gt;, and you’re already almost finished with supporting Dynamic Type in this screen.&lt;/p&gt;&lt;p&gt;Xcode has added the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Content Layout Guide&lt;/code&gt; and &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Frame Layout Guide&lt;/code&gt; when constraining &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;UI​Scroll​View&lt;/code&gt;s in iOS 11+. You can constrain the content, the &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;UI​Stack​View&lt;/code&gt;, to the &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;content​Layout​Guide&lt;/code&gt; top, bottom, leading, and trailing to define the scrollable area. You can then constrain the &lt;code index=&quot;11&quot; isInline=&quot;true&quot;&gt;UI​Stack​View&lt;/code&gt; ’s width to the &lt;code index=&quot;13&quot; isInline=&quot;true&quot;&gt;frame​Layout​Guide&lt;/code&gt; so that it only scrolls vertically. That’s it—your content can now scroll as it shrinks or grows!&lt;/p&gt;&lt;h4&gt;SwiftUI&lt;/h4&gt;&lt;p&gt;Depending on the styling of the screen and what version of SwiftUI you are using, you could potentially build this with a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Form&lt;/code&gt;. You can then place the few &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Text​Field&lt;/code&gt;s and a &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Button&lt;/code&gt; in it. That’s it—your content can now scroll as it shrinks or grows!&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6347f7c50c05c9085cdb0ad4fbba0042057771db-940x1070.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=235 235w, https://cdn.sanity.io/images/nkt6o869/production/6347f7c50c05c9085cdb0ad4fbba0042057771db-940x1070.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=470 470w, https://cdn.sanity.io/images/nkt6o869/production/6347f7c50c05c9085cdb0ad4fbba0042057771db-940x1070.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=705 705w, https://cdn.sanity.io/images/nkt6o869/production/6347f7c50c05c9085cdb0ad4fbba0042057771db-940x1070.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=940 940w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6347f7c50c05c9085cdb0ad4fbba0042057771db-940x1070.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=940&quot; width=&quot;940&quot; height=&quot;1070&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Recap&lt;/h3&gt;&lt;p&gt;Even the simplest screens in an app should be architected to allow for scrolling. That way, you generally don’t have to think about supporting the keyboard, different device sizes, or Dynamic Type. Your screens become more flexible and can continue to work as they change over the lifetime of the app. Making every single screen in your app scrollable will benefit you and your users—I hope I’ve convinced you to give it a try!&lt;/p&gt; </content:encoded><author>Michael Amundsen</author></item><item><title>Our Favorite Apps of 2021</title><link>https://lickability.com/blog/our-favorite-apps-of-2021/</link><guid isPermaLink="true">https://lickability.com/blog/our-favorite-apps-of-2021/</guid><description>The apps we loved this year</description><pubDate>Thu, 16 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We are, as you can probably guess, big fans of iOS apps here at Lickability. So here‘s a small selection of the apps we loved the most this year—some are new, some are just new to us, and some are old staples that we’re still enjoying.&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Gyroscope app icon&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/2da51f51f4e484f40cdb61fbc9e26164fc3d346d-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/2da51f51f4e484f40cdb61fbc9e26164fc3d346d-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/2da51f51f4e484f40cdb61fbc9e26164fc3d346d-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=512 512w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/2da51f51f4e484f40cdb61fbc9e26164fc3d346d-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;🩺 Gyroscope&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://gyrosco.pe/&quot;&gt;Gyroscope&lt;/a&gt; calls itself an “OS for your body,” and while that’s a pretty big claim, it isn’t far off. The app blends your health data, location data, sleep tracking, and even time tracking from RescueTime into a dashboard for your body and mind. I’ve used it extensively in the last year to get better sleep, work out more, and improve my nutrition. And with new features like AI food scanning and even human coaches, it just keeps getting better.&lt;/p&gt;&lt;p&gt;&lt;em&gt;— mb Bischoff&lt;/em&gt;&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Duolingo app icon&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/82afebb060995a475ed8769894ea248c1d136666-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/82afebb060995a475ed8769894ea248c1d136666-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/82afebb060995a475ed8769894ea248c1d136666-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=512 512w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/82afebb060995a475ed8769894ea248c1d136666-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;💬 Duolingo&lt;/h3&gt;&lt;p&gt;I discovered that you can use the Apple pencil on &lt;a href=&quot;https://www.duolingo.com/&quot;&gt;Duolingo&lt;/a&gt; to write in different languages during the exercises. I‘ve known from growing up that the only way I learn a language is if I write it and now I can do that 100% in this app instead of having to use a notebook at the same time.&lt;/p&gt;&lt;p&gt;&lt;em&gt;— Michael Amundsen&lt;/em&gt;&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Copilot app icon&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/77af4a8a53a7117dd303c4d6405e3082c34ae589-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/77af4a8a53a7117dd303c4d6405e3082c34ae589-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/77af4a8a53a7117dd303c4d6405e3082c34ae589-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=512 512w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/77af4a8a53a7117dd303c4d6405e3082c34ae589-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;💸 Copilot&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://copilot.money/&quot;&gt;Copilot&lt;/a&gt; gives my a simple and beautiful overview of my finances, and helps me see where my money is going. It is snappy, beautifully designed, and a breeze to setup and use. Budgets and categories are created automatically, and the only work I have to do is review the data and make decisions.&lt;/p&gt;&lt;p&gt;&lt;em&gt;— Tom DeVuono&lt;/em&gt;&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;GameTrack app icon&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/5cfa487620e6541e1772c61d3003d2d8c8ccbde0-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/5cfa487620e6541e1772c61d3003d2d8c8ccbde0-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/5cfa487620e6541e1772c61d3003d2d8c8ccbde0-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=512 512w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/5cfa487620e6541e1772c61d3003d2d8c8ccbde0-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;🎮 GameTrack&lt;/h3&gt;&lt;p&gt;This year, I upgraded from a spreadsheet to using &lt;a href=&quot;https://gametrack.app/&quot;&gt;GameTrack&lt;/a&gt; to track which video games I’m currently playing, want to play next, have finished, or have abandoned.&lt;/p&gt;&lt;p&gt;I used to tinker with the idea of building an app for myself to solve this problem, but in recent years, GameTrack has shown great commitment to new features and improvements, so I’m more than happy to give it a spot on my home screen. I especially like its statistics features, which include “Year In Review” at the perfect time to decide on my game of the year. The icing on the cake: it also works on Mac!&lt;/p&gt;&lt;p&gt;&lt;em&gt;— Michael Liberatore&lt;/em&gt;&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Power E*TRADE app icon&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c1a5a8e38f45225986a7a9225d16b10eb6429ff3-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/c1a5a8e38f45225986a7a9225d16b10eb6429ff3-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/c1a5a8e38f45225986a7a9225d16b10eb6429ff3-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=512 512w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c1a5a8e38f45225986a7a9225d16b10eb6429ff3-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;🚀 Power E*TRADE&lt;/h3&gt;&lt;p&gt;With meme stock mania this year, I upgraded my trading experience from Etrade‘s normal app to &lt;a href=&quot;https://apps.apple.com/us/app/power-e-trade-advanced-trading/id1111881020&quot;&gt;Power E*TRADE&lt;/a&gt;. It‘s a lot more specialized and has tons more data at your fingertips. I particularly like the paper money account you can set up through it to “practice” trading.&lt;/p&gt;&lt;p&gt;&lt;em&gt;— Michael Amundsen&lt;/em&gt;&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;TheGrint app icon&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c55acea6dd4efebae51adc07c4f011649d54fc1a-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/c55acea6dd4efebae51adc07c4f011649d54fc1a-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/c55acea6dd4efebae51adc07c4f011649d54fc1a-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=512 512w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c55acea6dd4efebae51adc07c4f011649d54fc1a-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;⛳️ TheGrint&lt;/h3&gt;&lt;p&gt;I use &lt;a href=&quot;https://thegrint.com/&quot;&gt;TheGrint&lt;/a&gt; as my score keeper and GPS app for golf. The app is simple to use, fast, and allows me to keep track of a fair number of stats for free. The GPS portion is great when playing unfamiliar courses and the added social features help me engage with my friends when they do well.&lt;/p&gt;&lt;p&gt;&lt;em&gt;— Tom DeVuono&lt;/em&gt;&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Opal app icon&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/3f915beacc3ffd54d058e19efa7f9c3975dc4339-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/3f915beacc3ffd54d058e19efa7f9c3975dc4339-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/3f915beacc3ffd54d058e19efa7f9c3975dc4339-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=512 512w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/3f915beacc3ffd54d058e19efa7f9c3975dc4339-512x512.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;📵 Opal&lt;/h3&gt;&lt;p&gt;I downloaded &lt;a href=&quot;https://www.opal.so&quot;&gt;Opal&lt;/a&gt; in an effort to reduce my screen time and use social media more mindfully. Opal lets you set up schedules for when to block certain apps, and if you‘d like to take a break from that schedule, you are prompted to type out a reason why you &lt;em&gt;absolutely need&lt;/em&gt; to use Twitter for 5 minutes. I can‘t say I’ve fully succeeded in reducing my screen time on a regular basis, but I‘ve enjoyed using the app either way.&lt;/p&gt;&lt;p&gt;&lt;em&gt;— Jillian Meehan&lt;/em&gt;&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Closing Our Office</title><link>https://lickability.com/blog/closing-our-office/</link><guid isPermaLink="true">https://lickability.com/blog/closing-our-office/</guid><description>An office epilogue</description><pubDate>Thu, 09 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Three years ago, we wrote &lt;a href=&quot;/blog/our-new-office-is-official&quot; title=&quot;Our New Office is Official&quot; data-astro-cid-okzjwv6e&gt;this&lt;/a&gt;  blog post:&lt;/p&gt;&lt;blockquote&gt;A few months ago, all seven of us were sitting side-by-side in a small co-working office. We had enough space for our desks and computers, but that was about it. Not exactly ideal. So, we decided to do something about it and started the search for a new office.&lt;/blockquote&gt;&lt;p&gt;We went from tiny coworking spaces to a full-sized office, and spent months working to make it feel like our own. We filled it with furniture, snacks, new team members, and friends. We had company game nights and weekly &lt;a href=&quot;/blog/the-magic-of-the-everybody-meeting&quot; title=&quot;The Magic of the Everybody Meeting&quot; data-astro-cid-okzjwv6e&gt;Everybody Meetings&lt;/a&gt;  and coffee breaks there. We &lt;a href=&quot;/blog/we-were-on-tv&quot; title=&quot;We Were On TV!&quot; data-astro-cid-okzjwv6e&gt;appeared on TV&lt;/a&gt;  there. We &lt;a href=&quot;/blog/10-years-of-lickability&quot; title=&quot;10 Years of Lickability&quot; data-astro-cid-okzjwv6e&gt;celebrated 10 years&lt;/a&gt;  of being a company there. And we had a pretty fun last &lt;a href=&quot;/blog/a-lickability-new-year&quot; title=&quot;A Lickability New Year&quot; data-astro-cid-okzjwv6e&gt;holiday party&lt;/a&gt;  there.&lt;/p&gt;&lt;p&gt;But all good things come to an end. At the beginning of 2020, our entire team (like much of the world) started working from home, not knowing when we‘d be able to return to the office. We switched to virtual meetings and game nights, perfected our at-home setups, and tried to plan for the eventual reopening of Lickability HQ.&lt;/p&gt;&lt;p&gt;Finally, in the early summer of this year, our team was fully vaccinated and ready to see each other again. We implemented our COVID safety policies, moved some furniture around, and invited everyone to come into the office if and when they wanted to. And while it was nice to see each other again and spend time in our old spot, it was clear by the end of the summer that the future of Lickability looked a lot different from pre-pandemic Lickability.&lt;/p&gt;&lt;p&gt;Adjusting to working from home after spending every day commuting into an office with the rest of your coworkers is tough. But adjusting to going back into the office after spending over a year working from home is just as hard. So as excited as we were to be able to reopen our office, we quickly found that we weren‘t getting nearly as much use out of it as we used to, with a lot of the company choosing to continue working from home most days of the week.&lt;/p&gt;&lt;p&gt;When our lease was up in September, we decided not to renew it. Instead, we‘re committing to making Lickability a great &lt;em&gt;remote&lt;/em&gt; place to work. We‘re continuing to help our team members build setups they love at home, planning virtual team lunches and other ways to stay connected with each other, and looking into options for drop-in spaces so we can meet up (or just get out of the house) occasionally. And we also just made our first fully remote hire!&lt;/p&gt;&lt;p&gt;It‘s bittersweet to say goodbye to our office—moving into that space was such an important part of Lickability’s history, and we have so many good memories there. But this is an important step forward for us, and we‘re excited to see where it takes us.&lt;/p&gt; </content:encoded><author>Jillian Meehan</author></item><item><title>Designing Mastodon’s App</title><link>https://lickability.com/blog/designing-mastodons-app/</link><guid isPermaLink="true">https://lickability.com/blog/designing-mastodons-app/</guid><description>Social networking, back in your hands</description><pubDate>Thu, 07 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At Lickability, we’ve long been interested in the power of social networks, which is why we’ve worked on apps like &lt;a href=&quot;https://houseparty.com/&quot;&gt;Houseparty&lt;/a&gt;, &lt;a href=&quot;https://tumblr.com/&quot;&gt;Tumblr&lt;/a&gt;, and &lt;a href=&quot;https://www.joinclubhouse.com/&quot;&gt;Clubhouse&lt;/a&gt; over the years. But there are few social networks where we’ve built a presence. In 2018, &lt;a href=&quot;https://mastodon.social/@lickability&quot;&gt;we joined Mastodon&lt;/a&gt;—a decentralized open-source social network from &lt;a href=&quot;https://mastodon.social/@Gargron&quot;&gt;Eugen Rochko&lt;/a&gt; (aka Gargron). And in 2021, Eugen came to us to design the official Mastodon iOS app. Then in February, the team released 1.0 of Mastodon for iOS on the App Store to &lt;a href=&quot;https://www.theverge.com/2021/7/30/22602275/mastodon-decentralized-social-network-official-ios-app-launches&quot;&gt;much fanfare&lt;/a&gt; and rave reviews.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f33ec30dfd725437bb1038904b3327dc8075dbb0-600x1298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w, https://cdn.sanity.io/images/nkt6o869/production/f33ec30dfd725437bb1038904b3327dc8075dbb0-600x1298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f33ec30dfd725437bb1038904b3327dc8075dbb0-600x1298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600&quot; width=&quot;600&quot; height=&quot;1298&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/66c9bb1e6673e3b3ef1dfc62328c276a5b703c26-600x1298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w, https://cdn.sanity.io/images/nkt6o869/production/66c9bb1e6673e3b3ef1dfc62328c276a5b703c26-600x1298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/66c9bb1e6673e3b3ef1dfc62328c276a5b703c26-600x1298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600&quot; width=&quot;600&quot; height=&quot;1298&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;While there have always been many third-party iOS apps for Mastodon on the App Store, Eugen wanted us to create a native first-party application that showcased what’s unique about Mastodon while also focusing on bringing in entirely new users. The app incorporates many of Mastodon’s signature features: robust privacy controls, custom profile fields, content warnings, polls, and custom emoji for each Mastodon server.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6983b0c5dfc8108b06c76eef8c1c0f41ba249526-600x1298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w, https://cdn.sanity.io/images/nkt6o869/production/6983b0c5dfc8108b06c76eef8c1c0f41ba249526-600x1298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6983b0c5dfc8108b06c76eef8c1c0f41ba249526-600x1298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600&quot; width=&quot;600&quot; height=&quot;1298&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/7a58a55e995095f9749e06cdbc4843f4ead7622a-600x1298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w, https://cdn.sanity.io/images/nkt6o869/production/7a58a55e995095f9749e06cdbc4843f4ead7622a-600x1298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/7a58a55e995095f9749e06cdbc4843f4ead7622a-600x1298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600&quot; width=&quot;600&quot; height=&quot;1298&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Our designer, &lt;a href=&quot;https://mastodon.online/@samhenrigold&quot;&gt;Sam Gold&lt;/a&gt;, drew inspiration from Mastodon’s existing web application, Apple’s Human Interface Guidelines, and decades of social media network design to craft something that looks at home on iOS but still feels like Mastodon. The app works in both light and dark mode and makes extensive use of context menus and haptics to feel fast and modern. Plus, it’s peppered with whimsical illustrations of actual mastodons from artist &lt;a href=&quot;https://mastodon.social/@dopatwo&quot;&gt;Dopatwo&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Working with the team at Mastodon and their development team at &lt;a href=&quot;https://sujitech.com/&quot;&gt;Sujitech&lt;/a&gt; was an absolute dream come true for us as longtime fans of the network’s ethos and ethics. And we’re not done yet. We’ve still got plenty of new features for the app up our sleeves in future releases, so stay tuned to the &lt;a href=&quot;https://blog.joinmastodon.org/&quot;&gt;Mastodon blog&lt;/a&gt; and &lt;a href=&quot;https://www.patreon.com/mastodon&quot;&gt;Patreon&lt;/a&gt; for updates.&lt;/p&gt;&lt;p&gt;Whether you’ve never used Mastodon before or you’re a longtime fan, &lt;a href=&quot;https://apps.apple.com/us/app/mastodon-for-iphone/id1571998974&quot;&gt;&lt;strong&gt;download the app&lt;/strong&gt;&lt;/a&gt; and find your community. Follow your friends and discover some new ones.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Need a brand new app designed, built, or both? We can help. &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Reach out now&lt;/a&gt; to chat with our team about projects starting in 2022.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>mb bischoff</author></item><item><title>5 Questions to Ask Before Building an App</title><link>https://lickability.com/blog/5-questions-to-ask-before-building-an-app/</link><guid isPermaLink="true">https://lickability.com/blog/5-questions-to-ask-before-building-an-app/</guid><description>Goals, platforms, capabilities, budget, &amp; release</description><pubDate>Mon, 16 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At Lickability, we‘ve helped lots of folks build and ship great apps. Because of this, many times when people meet with us for the first time, they ask us if there’s anything they‘re forgetting when thinking about starting work on their 1.0.&lt;/p&gt;&lt;p&gt;Here are &lt;strong&gt;5 of the most important questions&lt;/strong&gt; we think you should ask yourself before starting to build your app.&lt;/p&gt;&lt;h3&gt;What are your goals?&lt;/h3&gt;&lt;p&gt;First and foremost, you should think about your goals for the app, as these goals will drive many of the other decisions you‘ll have to make down the road.&lt;/p&gt;&lt;p&gt;Is the purpose of the app to make money right away, maybe by selling access to a product, service, or experience? Or is the goal to simply &lt;strong&gt;acquire users&lt;/strong&gt; in order to validate the idea and prove product market fit? Are you looking to &lt;strong&gt;develop a prototype&lt;/strong&gt;, launch an MVP and iterate on your idea (maybe even in a private beta), or &lt;strong&gt;launch a fully polished 1.0&lt;/strong&gt;?&lt;/p&gt;&lt;p&gt;Setting realistic and obtainable goals at the start of your app-building journey will give you a solid foundation on which to make future decisions and make many other questions clearer to answer.&lt;/p&gt;&lt;h3&gt;What platforms will you support?&lt;/h3&gt;&lt;p&gt;For some folks, the answer here may seem easy...support all the platforms! But, depending on the goals you‘ve established when answering the previous question, that may not be the right answer for your project.&lt;/p&gt;&lt;p&gt;When thinking about mobile apps, there are two main players: iOS and Android. But with this simple choice comes many other decision points, such as whether to write the apps natively or use a cross-platform technology. For folks that are starting from scratch, our recommendation is to pick a single platform to nail the experience first. This is usually the quickest and least expensive option, and allows you to get your product out in the world to start getting feedback from users.&lt;/p&gt;&lt;p&gt;If one of your goals is to reach as many people as possible at launch, then supporting both platforms out of the gate may be best. If this is the case, you‘ll have a choice to make about how the app is created. If performance and polish are important to achieving your goal, writing the app natively for each platform is your best choice, as it lets you take advantage of everything the hardware and system OS has to offer. If cost and speed are the main factor, taking advantage of a cross-platform technology (in short, one codebase for both platforms) may be the right call. However, a word of caution we like to make our clients aware of is that using cross-platform technologies is not as simple as getting two apps for the price of one. You’ll still have to consider differences in design across supported devices, and you‘ll need some native code to make specific features work on each platform.&lt;/p&gt;&lt;p&gt;In addition to the mobile platform you choose, you may want to evaluate if supporting other platforms for tablets and wearables makes sense. Finally, don‘t forget the other technologies in the stack that will power your app—a content management system to add content to the app, a backend for user accounts, and user analytics and crash reporting to monitor performance.&lt;/p&gt;&lt;h3&gt;What can you do yourself and what do you need help with?&lt;/h3&gt;&lt;p&gt;Building a mobile app from scratch is hard work. It would be wise to perform a self-evaluation of what your capabilities are, both in skill and in available time. This will help you put together a clear list of what work you can take care of in-house and what help you‘ll need to seek out.&lt;/p&gt;&lt;p&gt;Some services to keep in mind:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Design&lt;/li&gt;&lt;li&gt;Frontend &amp;amp; Backend Development&lt;/li&gt;&lt;li&gt;Project Management&lt;/li&gt;&lt;li&gt;Product Management&lt;/li&gt;&lt;li&gt;Customer Support&lt;/li&gt;&lt;li&gt;Marketing/Social Media&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This process also leads well into the next important question...&lt;/p&gt;&lt;h3&gt;What is your budget?&lt;/h3&gt;&lt;p&gt;One of the most difficult (and most important) questions to answer revolves around the triple constraints: time, cost, and scope. This project management triangle applies to any project you could ever undertake—from redoing a kitchen sink in your house to building the next Facebook.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;The project management triangle. Scope, cost, and time are mapped onto one of the angles. The center of the triangle is labeled “Quality”&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a8ac0bbfbec9d576f0d5a0d442e046c0b2e788bf-1200x1007.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w, https://cdn.sanity.io/images/nkt6o869/production/a8ac0bbfbec9d576f0d5a0d442e046c0b2e788bf-1200x1007.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/a8ac0bbfbec9d576f0d5a0d442e046c0b2e788bf-1200x1007.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=900 900w, https://cdn.sanity.io/images/nkt6o869/production/a8ac0bbfbec9d576f0d5a0d442e046c0b2e788bf-1200x1007.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a8ac0bbfbec9d576f0d5a0d442e046c0b2e788bf-1200x1007.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200&quot; width=&quot;1200&quot; height=&quot;1007&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Time can be reduced by adding more engineers to the project (and adding cost) or reducing scope. One thing to keep in mind here is that there are diminishing returns on adding engineers to lower the development time, as only so much work can be done in parallel.&lt;/p&gt;&lt;p&gt;Scope will largely be determined by the goals for the app. As much as we may want to include every feature possible, the reality is you might have to make some difficult decisions here. However, don‘t forget that you can always add more features later in the app’s roadmap.&lt;/p&gt;&lt;p&gt;Mobile app development is expensive, so keeping costs in check is an important factor. Generally, reducing scope is the most effective way to reduce costs, but increasing development time can also be a good way to save a little bit of money.&lt;/p&gt;&lt;p&gt;Putting together your budget is also a good time to think about your funding. There are several options to fund a project, that all carry their own pros and cons. You could raise venture capital funds, crowdfund your idea on platforms such as &lt;a href=&quot;https://kickstarter.com&quot;&gt;Kickstarter&lt;/a&gt;, look for angel investors, seek out funding from friends &amp;amp; family, or bootstrap the project yourself to name a few possible solutions.&lt;/p&gt;&lt;h3&gt;What are your plans after the app is released?&lt;/h3&gt;&lt;p&gt;Finally, take some time to think about your post-launch plan. Software is never perfect and always changing, so what will your plan be for maintenance &amp;amp; bug fixes? When users finally get to play with your app, they will have feedback. How will you address this feedback and provide support for customers who need it? Are you prepared to support your app if it explodes in popularity? What about additional feature development, whether that be items on your roadmap or user requested features? Do you plan on taking over ownership of the app, hiring a full-time team, or continuing to use a partner?&lt;/p&gt;&lt;p&gt;Answering these questions should put you well on your way to bringing your idea to life. Whether you need help answering these questions yourself, or you are ready to get started, we‘d love to chat with you and help however we can!&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Want help building &amp;amp; shipping an iOS app? We can help! &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Get in touch&lt;/a&gt;.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Thomas DeVuono</author></item><item><title>Automating App Store Screenshots</title><link>https://lickability.com/blog/automating-app-store-screenshots-with-fastlane-and-swiftui/</link><guid isPermaLink="true">https://lickability.com/blog/automating-app-store-screenshots-with-fastlane-and-swiftui/</guid><description>Using Fastlane and SwiftUI</description><pubDate>Mon, 02 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;Why are screenshots important?&lt;/h3&gt;&lt;p&gt;You’re probably here because you might be shipping to the App Store soon or are coming close to App Store submission. 🚀 Truly an exciting part of the app creation process! One very important part of the App Store submission process is taking screenshots of key features in your app. Screenshots help potential users visualize what functionality to expect when downloading your app for the first time. Taking screenshots can also be a very time consuming part of the process—luckily there is a nifty tool from Fastlane to help automate this process called &lt;a href=&quot;https://docs.fastlane.tools/actions/snapshot/&quot;&gt;snapshot&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Getting started with Fastlane&lt;/h3&gt;&lt;p&gt;You’ll need to have Xcode command line tools installed, but in case you don’t, entering this command via Terminal should do it:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Shell&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;xcode-select&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --install&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Fastlane can be installed through multiple clients including &lt;a href=&quot;https://formulae.brew.sh/formula/fastlane&quot;&gt;Homebrew&lt;/a&gt;:&lt;/p&gt;&lt;blockquote class=&quot;quote-block&quot;&gt; &lt;p class=&quot;quote-text&quot;&gt;fastlane can be installed multiple ways. The preferred method is with Bundler.&lt;/p&gt; &lt;a href=&quot;https://docs.fastlane.tools/getting-started/ios/setup/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; &lt;cite class=&quot;quote-author&quot;&gt;Fastlane&amp;#39;s setup documentation&lt;/cite&gt; &lt;/a&gt; &lt;/blockquote&gt;&lt;p&gt;These are the instructions from the documentation:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Install Bundler by running &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;gem install bundler&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Create a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;./Gemfile&lt;/code&gt; in the root directory of your project with the content&lt;/li&gt;&lt;/ul&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Shell&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;https://rubygems.org&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;gem&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;fastlane&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;h3&gt;Setting up Fastlane snapshots&lt;/h3&gt;&lt;p&gt;Running this command will generate the Swift &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Snapshot​Helper&lt;/code&gt; and &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Snapfile&lt;/code&gt; necessary for configuring metadata related to your screenshots:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Shell&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fastlane&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; snapshot&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; init&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;The generated Snapfile will look similar to this:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c0f645e3f0964a69608d86afba7c0edc234f79af-795x643.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=199 199w, https://cdn.sanity.io/images/nkt6o869/production/c0f645e3f0964a69608d86afba7c0edc234f79af-795x643.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=398 398w, https://cdn.sanity.io/images/nkt6o869/production/c0f645e3f0964a69608d86afba7c0edc234f79af-795x643.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=596 596w, https://cdn.sanity.io/images/nkt6o869/production/c0f645e3f0964a69608d86afba7c0edc234f79af-795x643.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=795 795w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c0f645e3f0964a69608d86afba7c0edc234f79af-795x643.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=795&quot; width=&quot;795&quot; height=&quot;643&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;You’ll have to change the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Scheme​Name&lt;/code&gt; to the project test target name. For reference we’ll also drag the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Snapshot​Helper&lt;/code&gt; Swift file to the UI test target folder, although it can remain anywhere in the project directory.&lt;/p&gt;&lt;p&gt;If you’re curious about which APIs are used for the snapshots, you can read more about how &lt;a href=&quot;https://developer.apple.com/documentation/xctest/xcuiscreenshot&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;XCUI​Screenshot&lt;/code&gt;&lt;/a&gt; is used for generating screenshots. The &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Snapshot​Helper&lt;/code&gt; also helps scale the snapshots based on device orientation and size using &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uigraphicsimagerendererformat&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;UI​Graphics​Image​Renderer​Format&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Based on the platforms your app supports, the device list can expand or decrease. For now we’ll choose iPhone 12 Pro Max, iPhone SE (2nd generation), iPad Pro (12.9-inch) (4th generation) to generate the snapshots in the Snapfile.&lt;/p&gt;&lt;p&gt;You can also also download additional simulators or change the device list to match the simulators available. Otherwise Fastlane will produce an error when attempting to set those values.&lt;/p&gt;&lt;p&gt;For simplicity we’ll set up the Snapfile like this:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;# Uncomment the lines below you want to change by removing the # &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; the beginning&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;# A list of devices you want to take the screenshots from&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; devices&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;([&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;   &quot;iPhone 12 Pro Max&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;   &quot;iPhone SE (2nd generation)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;   &quot;iPad Pro (12.9-inch) (4th generation)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; languages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;([&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;   &quot;en-US&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;# The name of the scheme which contains the UI Tests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; scheme&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;FastlaneSnapshotsUITests&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;# Where should the resulting screenshots be stored?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; output_directory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;./screenshots&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;# remove the &apos;#&apos; to clear all previously generated screenshots before creating new ones&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; clear_previous_screenshots&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;# Remove the &apos;#&apos; to &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; the status bar to &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;41&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; AM, and show full battery and reception.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; override_status_bar&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;h3&gt;Setting up Xcode&lt;/h3&gt;&lt;p&gt;We made an Xcode project with all of the examples shown in this post. You can find it right &lt;a href=&quot;https://github.com/Lickability/FastlaneSnapshotsDemo&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;An important part of the Fastlane automation tool for snapshots is setting the UI test target to build and run like this:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/74e7dfd53de541b588c17c284dabe2ec206b34c6-937x522.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=234 234w, https://cdn.sanity.io/images/nkt6o869/production/74e7dfd53de541b588c17c284dabe2ec206b34c6-937x522.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=469 469w, https://cdn.sanity.io/images/nkt6o869/production/74e7dfd53de541b588c17c284dabe2ec206b34c6-937x522.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=703 703w, https://cdn.sanity.io/images/nkt6o869/production/74e7dfd53de541b588c17c284dabe2ec206b34c6-937x522.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=937 937w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/74e7dfd53de541b588c17c284dabe2ec206b34c6-937x522.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=937&quot; width=&quot;937&quot; height=&quot;522&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Configuring the UI test&lt;/h3&gt;&lt;p&gt;Next, we’ll need to set up the UI test code with an &lt;a href=&quot;https://developer.apple.com/documentation/xctest/xcuiapplication&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;XCUI​Application&lt;/code&gt;&lt;/a&gt; instance along with a helper function from the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Snapshot​Helper&lt;/code&gt; like this:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; app: XCUIApplication!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;override&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setUp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setUp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    continueAfterFailure = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    app = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;XCUIApplication&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    setupSnapshot&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(app)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    app.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;launch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;One thing to note here is that UI tests can be time consuming depending on the complexity and architecture of your app. In order to create meaningful App Store screenshots, you’ll need some variation of mock data. When building &lt;a href=&quot;https://lickability.com/blog/scorecard-is-here/&quot;&gt;Scorecard&lt;/a&gt; we found &lt;a href=&quot;https://developer.apple.com/documentation/xctest/xcuiapplication/1500477-launcharguments&quot;&gt;launch arguments&lt;/a&gt; super useful to help differentiate the UI test at app launch. Then you can simply add an extension like this to detect the launch argument:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;extension&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; UIApplication&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    /// Checks the command line arguments for the current process to see if a UI test is running.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; isUITest: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Bool&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; CommandLine.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;arguments&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;contains&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;-UITests&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;For simplicity, the demo project contains a SwiftUI &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/list&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;List&lt;/code&gt;&lt;/a&gt;, avatar image, stepper, and label for the stepper count. I’ve also included a navigation item to display a text field alert to change the title of the list. The &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Snapshot​Helper&lt;/code&gt; we covered earlier contains this function that takes the screenshot and append a name to each one:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// - Parameters:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;///   - name: The name of the snapshot&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;///   - timeout: Amount of seconds to wait until the network loading indicator disappears. Pass `0` if you don&apos;t want to wait.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; snapshot&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;timeWaitingForIdle&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; timeout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: TimeInterval = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    Snapshot.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;snapshot&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(name, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;timeWaitingForIdle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: timeout)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;The UI test code will then look something like this:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; testTakeSnapshots&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    snapshot&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;1-GameView&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; coordinate = app.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;tables&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;otherElements&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;firstMatch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;coordinate&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;withNormalizedOffset&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CGVector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dx&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.9&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; _&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        coordinate.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    snapshot&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;2-StepperIncremented&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    app.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;navigationBars&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;firstMatch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;buttons&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;firstMatch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    app.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;textFields&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;firstMatch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    snapshot&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;3-Alert&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    app.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;textFields&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;buttons&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Clear text&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tapElement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    app.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;typeText&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Taboo&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    app.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;buttons&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Dismiss&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    snapshot&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;4-GameChanged&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;h3&gt;Generating the snapshots&lt;/h3&gt;&lt;p&gt;After confirming that this builds and tests succeed for each device in the Snapfile list, we’ll run:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Shell&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bundle&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; exec&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; fastlane&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; snapshot&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --verbose&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;The &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;--verbose&lt;/code&gt; flag is great for identifying build or test failures. This might also take a few minutes. ⏳&lt;/p&gt;&lt;p&gt;Once the snapshots are generated you’ll see a nice results table indicating success or failure.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e48bf7b11b166d8bd4094527339c37dd25f4c690-355x157.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=178 178w, https://cdn.sanity.io/images/nkt6o869/production/e48bf7b11b166d8bd4094527339c37dd25f4c690-355x157.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=355 355w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e48bf7b11b166d8bd4094527339c37dd25f4c690-355x157.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=355&quot; width=&quot;355&quot; height=&quot;157&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;You’ll also see a local html file with the snapshots in the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;/screenshots&lt;/code&gt; directory.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/3cef177057f52ea0f150b657119eccc960813d39-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/3cef177057f52ea0f150b657119eccc960813d39-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/3cef177057f52ea0f150b657119eccc960813d39-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/3cef177057f52ea0f150b657119eccc960813d39-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/3cef177057f52ea0f150b657119eccc960813d39-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/3cef177057f52ea0f150b657119eccc960813d39-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/3cef177057f52ea0f150b657119eccc960813d39-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2560 2560w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/3cef177057f52ea0f150b657119eccc960813d39-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;900&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;These look fine but they’re unframed. Luckily Fastlane has another nifty command line tool to frame these screenshots into iOS device frames called &lt;a href=&quot;https://docs.fastlane.tools/actions/frameit/&quot;&gt;frameit&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Using Frameit&lt;/h3&gt;&lt;p&gt;You’ll need to download the frames first:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Shell&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bundle&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; exec&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; fastlane&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; frameit&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; download_frames&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Then:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Shell&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bundle&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; exec&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; fastlane&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; frameit&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;After that runs, navigate to the screenshots directory and you can see that those framed screenshots were added separately.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9154fe70e55c5715c198f7cd275e416ba759caa8-718x537.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=359 359w, https://cdn.sanity.io/images/nkt6o869/production/9154fe70e55c5715c198f7cd275e416ba759caa8-718x537.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=718 718w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9154fe70e55c5715c198f7cd275e416ba759caa8-718x537.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=718&quot; width=&quot;718&quot; height=&quot;537&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;From Fastlane’s &lt;a href=&quot;https://docs.fastlane.tools/actions/frameit/#usage&quot;&gt;advanced frameit usage&lt;/a&gt; documentation:&lt;/p&gt;&lt;blockquote class=&quot;quote-block&quot;&gt; &lt;p class=&quot;quote-text&quot;&gt;With frameit it&amp;#39;s possible to add a custom background and text below or above the framed screenshots in fonts and colors you define.&lt;/p&gt; &lt;a href=&quot;https://docs.fastlane.tools/actions/frameit/#usage&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; &lt;cite class=&quot;quote-author&quot;&gt;Fastlane&lt;/cite&gt; &lt;/a&gt; &lt;/blockquote&gt;&lt;p&gt;To make this possible we need to create a json file named Framefile with the following structure:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;JSON&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;device_frame_version&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;latest&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;default&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    &quot;title&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      &quot;font&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;./Fonts/SF-Pro-Rounded-Bold.otf&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      &quot;color&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;#FFFFFF&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    &quot;padding&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    &quot;show_complete_frame&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    &quot;stack_title&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    &quot;use_platform&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;IOS&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;data&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      &quot;filter&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;1-GameView&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      &quot;background&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;./red.png&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      &quot;filter&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;2-StepperIncremented&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      &quot;background&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;./blue.png&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      &quot;filter&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;3-Alert&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      &quot;background&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;./yellow.png&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      &quot;filter&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;4-GameChanged&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      &quot;background&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;./green.png&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;In the docs, the general parameters are listed with a description indicating if it’s optional or mandatory. The background key is mandatory and must be an image file, e.g. png, jpg. I’ve included some sample colors in the screenshots folder along with a custom font.&lt;/p&gt;&lt;p&gt;Notice how the filter key includes the named screenshots from the UI test. We’ll use that key to associate it with a title in a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;title.strings&lt;/code&gt; file like this:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Yaml&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;GameView&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; = &quot;Keep score of your favorite games&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;StepperIncremented&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; = &quot;Watch the scoreboard update in real time&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Alert&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; = &quot;Modify games on any device&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;GameChanged&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; = &quot;Track play history with sessions&quot;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Then run:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Shell&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bundle&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; exec&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; fastlane&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; frameit&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;The result: framed screenshots with the parameters we defined!&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/96baf062a17bd088bc09113a6af4ed1a8924f436-948x516.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=237 237w, https://cdn.sanity.io/images/nkt6o869/production/96baf062a17bd088bc09113a6af4ed1a8924f436-948x516.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=474 474w, https://cdn.sanity.io/images/nkt6o869/production/96baf062a17bd088bc09113a6af4ed1a8924f436-948x516.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=711 711w, https://cdn.sanity.io/images/nkt6o869/production/96baf062a17bd088bc09113a6af4ed1a8924f436-948x516.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=948 948w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/96baf062a17bd088bc09113a6af4ed1a8924f436-948x516.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=948&quot; width=&quot;948&quot; height=&quot;516&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;Fastlane’s snapshot and frameit automation tools are incredibly useful for App Store screenshots and App Previews. It’s easily integratabtle through the command line and Xcode. This setup and configuration can be adopted into new or existing Xcode projects. Like many software tools, these can also be improved. For instance, the titles can only be one color—it would be nice if there was some attributed support for highlighting specific text. Having the background colors as image files work well, but having extended configurations for hex or RGB strings would be more customizable. Nonetheless, we’re fans of the Fastlane tool suite and hope this post helps in your app submission process!&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Want help building &amp;amp; shipping an iOS app? We can help! &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Get in touch&lt;/a&gt;.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Daisy Ramos</author></item><item><title>How to recover and export your quotes from Quotebook</title><link>https://lickability.com/blog/quotebook-support/</link><guid isPermaLink="true">https://lickability.com/blog/quotebook-support/</guid><description>A guide for iOS 14.5+</description><pubDate>Tue, 06 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Unfortunately, &lt;a href=&quot;https://lickability.com/blog/the-end-of-quotebook/&quot;&gt;we no longer develop or actively support Quotebook&lt;/a&gt; and we removed it from the App Store years ago.&lt;/p&gt;&lt;p&gt;With the release of iOS 14.5, Apple had to update many older apps so they could continue to run. &lt;strong&gt;To download a copy of Quotebook that you can open in order to export your quotes:&lt;/strong&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Attach your device to your computer and use Finder (on macOS) or iTunes (on Windows) and &lt;a href=&quot;https://support.apple.com/en-us/HT210598&quot;&gt;copy your .quotebook files&lt;/a&gt; to your computer as a backup&lt;/li&gt;&lt;li&gt;Confirm that you were using iCloud syncing for Quotebook in the Settings app (Settings → Your profile at the top → iCloud → Confirm that the switch next to Quotebook is on)&lt;/li&gt;&lt;li&gt;Uninstall Quotebook from your device&lt;/li&gt;&lt;li&gt;Redownload Quotebook from the App Store &lt;a href=&quot;https://support.apple.com/en-us/HT211841&quot;&gt;“Purchased” list&lt;/a&gt; by searching for it&lt;/li&gt;&lt;li&gt;Open the app and wait for iCloud to sync (this can take up to 20 minutes)&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;strong&gt;To export your quotes from Quotebook and move them to another system:&lt;/strong&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Open Quotebook&lt;/li&gt;&lt;li&gt;Tap the settings gear in the top left&lt;/li&gt;&lt;li&gt;Tap “Backup &amp;amp; Restore”&lt;/li&gt;&lt;li&gt;Tap “Export CSV”&lt;/li&gt;&lt;li&gt;Choose “Email”&lt;/li&gt;&lt;li&gt;Email the CSV to yourself&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;I hope this helps you regain access to your quotes, and if you have any trouble or further questions, don‘t hesitate to reach out. Thank you so much for being a longtime customer, supporter, and user of Quotebook.&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Conferences Condensed: WWDC21</title><link>https://lickability.com/blog/conferences-condensed-wwdc21/</link><guid isPermaLink="true">https://lickability.com/blog/conferences-condensed-wwdc21/</guid><description>A few of our favorite sessions</description><pubDate>Tue, 22 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Another year, another great WWDC. The Lickability team watched a lot of videos from this year‘s conference in our &lt;a href=&quot;https://discord.gg/K65tPX5gSJ&quot;&gt;Discord&lt;/a&gt;—but here are a few that we loved the most.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10022/&quot;&gt;Demystify SwiftUI&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Many Lickability engineers have taken the leap into SwiftUI on client and internal projects, so needless to say we were eager to watch any sessions that focused on this year’s improvements to the framework. However, the session that stuck out to me the most wasn’t focused on the introduction of new APIs. &lt;strong&gt;Demystify SwiftUI&lt;/strong&gt; has re-shaped the way I think about building SwiftUI views, with a focus on performance and correctness.&lt;/p&gt;&lt;p&gt;SwiftUI promises the ability to describe your UI and let the framework figure out the best approach to build, render, and animate it. In practice, however, having an understanding of exactly how its performant view diffing works, including view identity and the lifetime of view state, is essential for efficient output, accurate animations, and preventing tricky bugs. Until now, this was simply (educated) guesswork about what was going on under the hood, so it’s so nice to finally have the answers on how to rely on SwiftUI’s strengths. After this session, you may think twice about unnecessary branching and type-erasing with &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Any​View&lt;/code&gt;!&lt;/p&gt;&lt;p&gt;— &lt;em&gt;Michael Liberatore&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10252/&quot;&gt;Make blazing fast lists and collection views&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;iOS 15 introduces &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;prepare​Thumbnail(of size: CG​Size)&lt;/code&gt; for efficiency with large images that can scale and prepare an image to a smaller size. Which saves a lot of CPU time and memory.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Using prepareThumbnail&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Initialize the full image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; profileImage = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UIImage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(...)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Set a placeholder before preparation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;posterAvatarView.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;image&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = placeholderImage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Prepare the image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;profileImage.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;prepareThumbnail&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;of&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: posterAvatarView.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;bounds&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) { thumbnailImage &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  DispatchQueue.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;posterAvatarView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;image&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = thumbnailImage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;At one point or another, almost every iOS developer comes across image loading performance bugs or hitches. This new API looks like a promising enhancement for asynchronous content in collection or list views.&lt;/p&gt;&lt;p&gt;— &lt;em&gt;Daisy Ramos&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://developer.apple.com/wwdc21/10304&quot;&gt;The process of inclusive design&lt;/a&gt; and &lt;a href=&quot;https://developer.apple.com/wwdc21/10275&quot;&gt;The practice of inclusive design&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;The design talks at WWDC are always my favorite, and this year was no exception. The pair of talks on inclusive design were incredibly well thought-out looks at the axes of inclusion that designers can and should consider when designing apps and their intersections. While we often talk about accessibility as one of these areas, but Apple took time to cover religion, race, class, gender and sexuality and how they impact the experiences our users have in apps. There’s even a new &lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/inclusion/overview&quot;&gt;Inclusion&lt;/a&gt; section in the Human Interface Guidelines.&lt;/p&gt;&lt;div class=&quot;not-prose&quot;&gt; &lt;style&gt;astro-island,astro-slot,astro-static-slot{display:contents}&lt;/style&gt;&lt;script&gt;(()=&gt;{var l=(s,i,o)=&gt;{let r=async()=&gt;{await(await s())()},t=typeof i.value==&quot;object&quot;?i.value:void 0,c={rootMargin:t==null?void 0:t.rootMargin},n=new IntersectionObserver(e=&gt;{for(let a of e)if(a.isIntersecting){n.disconnect(),r();break}},c);for(let e of o.children)n.observe(e)};(self.Astro||(self.Astro={})).visible=l;window.dispatchEvent(new Event(&quot;astro:visible&quot;));})();;(()=&gt;{var A=Object.defineProperty;var g=(i,o,a)=&gt;o in i?A(i,o,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[o]=a;var d=(i,o,a)=&gt;g(i,typeof o!=&quot;symbol&quot;?o+&quot;&quot;:o,a);{let i={0:t=&gt;m(t),1:t=&gt;a(t),2:t=&gt;new RegExp(t),3:t=&gt;new Date(t),4:t=&gt;new Map(a(t)),5:t=&gt;new Set(a(t)),6:t=&gt;BigInt(t),7:t=&gt;new URL(t),8:t=&gt;new Uint8Array(t),9:t=&gt;new Uint16Array(t),10:t=&gt;new Uint32Array(t),11:t=&gt;1/0*t},o=t=&gt;{let[l,e]=t;return l in i?i[l](e):void 0},a=t=&gt;t.map(o),m=t=&gt;typeof t!=&quot;object&quot;||t===null?t:Object.fromEntries(Object.entries(t).map(([l,e])=&gt;[l,o(e)]));class y extends HTMLElement{constructor(){super(...arguments);d(this,&quot;Component&quot;);d(this,&quot;hydrator&quot;);d(this,&quot;hydrate&quot;,async()=&gt;{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest(&quot;astro-island[ssr]&quot;);if(e){e.addEventListener(&quot;astro:hydrate&quot;,this.hydrate,{once:!0});return}let c=this.querySelectorAll(&quot;astro-slot&quot;),n={},h=this.querySelectorAll(&quot;template[data-astro-template]&quot;);for(let r of h){let s=r.closest(this.tagName);s!=null&amp;&amp;s.isSameNode(this)&amp;&amp;(n[r.getAttribute(&quot;data-astro-template&quot;)||&quot;default&quot;]=r.innerHTML,r.remove())}for(let r of c){let s=r.closest(this.tagName);s!=null&amp;&amp;s.isSameNode(this)&amp;&amp;(n[r.getAttribute(&quot;name&quot;)||&quot;default&quot;]=r.innerHTML)}let p;try{p=this.hasAttribute(&quot;props&quot;)?m(JSON.parse(this.getAttribute(&quot;props&quot;))):{}}catch(r){let s=this.getAttribute(&quot;component-url&quot;)||&quot;&lt;unknown&gt;&quot;,v=this.getAttribute(&quot;component-export&quot;);throw v&amp;&amp;(s+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${s}`,this.getAttribute(&quot;props&quot;),r),r}let u;await this.hydrator(this)(this.Component,p,n,{client:this.getAttribute(&quot;client&quot;)}),this.removeAttribute(&quot;ssr&quot;),this.dispatchEvent(new CustomEvent(&quot;astro:hydrate&quot;))});d(this,&quot;unmount&quot;,()=&gt;{this.isConnected||this.dispatchEvent(new CustomEvent(&quot;astro:unmount&quot;))})}disconnectedCallback(){document.removeEventListener(&quot;astro:after-swap&quot;,this.unmount),document.addEventListener(&quot;astro:after-swap&quot;,this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute(&quot;await-children&quot;)||document.readyState===&quot;interactive&quot;||document.readyState===&quot;complete&quot;)this.childrenConnectedCallback();else{let e=()=&gt;{document.removeEventListener(&quot;DOMContentLoaded&quot;,e),c.disconnect(),this.childrenConnectedCallback()},c=new MutationObserver(()=&gt;{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&amp;&amp;this.lastChild.nodeValue===&quot;astro:end&quot;&amp;&amp;(this.lastChild.remove(),e())});c.observe(this,{childList:!0}),document.addEventListener(&quot;DOMContentLoaded&quot;,e)}}async childrenConnectedCallback(){let e=this.getAttribute(&quot;before-hydration-url&quot;);e&amp;&amp;await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute(&quot;opts&quot;)),c=this.getAttribute(&quot;client&quot;);if(Astro[c]===void 0){window.addEventListener(`astro:${c}`,()=&gt;this.start(),{once:!0});return}try{await Astro[c](async()=&gt;{let n=this.getAttribute(&quot;renderer-url&quot;),[h,{default:p}]=await Promise.all([import(this.getAttribute(&quot;component-url&quot;)),n?import(n):()=&gt;()=&gt;{}]),u=this.getAttribute(&quot;component-export&quot;)||&quot;default&quot;;if(!u.includes(&quot;.&quot;))this.Component=h[u];else{this.Component=h;for(let f of u.split(&quot;.&quot;))this.Component=this.Component[f]}return this.hydrator=p,this.hydrate},e,this)}catch(n){console.error(`[astro-island] Error hydrating ${this.getAttribute(&quot;component-url&quot;)}`,n)}}attributeChangedCallback(){this.hydrate()}}d(y,&quot;observedAttributes&quot;,[&quot;props&quot;]),customElements.get(&quot;astro-island&quot;)||customElements.define(&quot;astro-island&quot;,y)}})();&lt;/script&gt;&lt;astro-island uid=&quot;1MFxsY&quot; prefix=&quot;r0&quot; component-url=&quot;/opt/build/repo/embeds/ClientEmbed.tsx&quot; component-export=&quot;ClientEmbed&quot; renderer-url=&quot;@astrojs/react/client.js&quot; props=&quot;{&amp;quot;providerId&amp;quot;:[0,&amp;quot;twitter&amp;quot;],&amp;quot;wrapperClass&amp;quot;:[0,&amp;quot;aspect-video&amp;quot;],&amp;quot;tweetLink&amp;quot;:[0,&amp;quot;mb/status/1402347482277986313&amp;quot;]}&quot; ssr=&quot;&quot; client=&quot;visible&quot; before-hydration-url=&quot;astro:scripts/before-hydration.js&quot; opts=&quot;{&amp;quot;name&amp;quot;:&amp;quot;ClientEmbed&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;rootMargin&amp;quot;:&amp;quot;400px&amp;quot;}}&quot; await-children=&quot;&quot;&gt;&lt;div class=&quot;aspect-video&quot;&gt;&lt;div data-testid=&quot;general-observer&quot; class=&quot;mdx-embed&quot;&gt;&lt;div style=&quot;height:0;width:100%&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--astro:end--&gt;&lt;/astro-island&gt; &lt;/div&gt;&lt;p&gt;— &lt;em&gt;mb Bischoff&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Mines a two-parter: &lt;strong&gt;The practice of inclusive design&lt;/strong&gt; and &lt;strong&gt;The process of inclusive design.&lt;/strong&gt; Here are some of my takeaways:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Reach the largest audience by being inclusive&lt;/li&gt;&lt;li&gt;You want a user to feel like you designed your app with them in mind&lt;/li&gt;&lt;li&gt;Design is not just how the app looks; it’s the content (words, images, audio, video, and accessibility features you implement)&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;6 practices&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;Tell diverse stories&lt;/li&gt;&lt;li&gt;Avoid stereotypes&lt;/li&gt;&lt;li&gt;Adopt accessibility&lt;/li&gt;&lt;li&gt;Localize for culture&lt;/li&gt;&lt;li&gt;Use color mindfully&lt;/li&gt;&lt;li&gt;Encourage self-expression&lt;/li&gt;&lt;/ol&gt;&lt;h4&gt;Inclusive design is better for everyone&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Empower and delight&lt;/li&gt;&lt;li&gt;Inclusion is a journey&lt;/li&gt;&lt;li&gt;Build diverse teams and empower them&lt;/li&gt;&lt;li&gt;See differences as an asset&lt;/li&gt;&lt;li&gt;Budget time and space to think inclusively, it doesn’t come for free&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;— &lt;em&gt;Thomas Devuono&lt;/em&gt;&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Let us know what your favorite WWDC21 sessions were—we‘d love to hear from you on &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;Twitter&lt;/a&gt;!&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Team Lickability</author></item><item><title>🐞 Insidious Bugs #3: Apple App Site Association File Identifiers</title><link>https://lickability.com/blog/insidious-bugs-number-3-apple-app-site-association-file/</link><guid isPermaLink="true">https://lickability.com/blog/insidious-bugs-number-3-apple-app-site-association-file/</guid><description>Troubleshooting Password AutoFill and Universal Links</description><pubDate>Tue, 25 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;&lt;a href=&quot;https://lickability.com/blog/categories/insidious-bugs&quot;&gt;Insidious Bugs&lt;/a&gt; is an occasional series in which we share stories of overcoming obscure issues in iOS development with the goal of saving you the time we lost, should you encounter the issues yourself.&lt;/p&gt;  &lt;/aside&gt;&lt;h3&gt;The Bug&lt;/h3&gt;&lt;p&gt;When creating an &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;apple-app-site-association&lt;/code&gt; file, the &lt;a href=&quot;https://developer.apple.com/documentation/xcode/supporting-associated-domains&quot;&gt;documentation&lt;/a&gt; says to use your &lt;strong&gt;Application Identifier Prefix&lt;/strong&gt; followed by the &lt;strong&gt;Bundle Identifier&lt;/strong&gt; for your &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;app​I​Ds&lt;/code&gt; key.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;&amp;lt;Application Identifier Prefix&amp;gt;.&amp;lt;Bundle Identifier&amp;gt;&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;applinks&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      &quot;details&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;              &quot;appIDs&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [ &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;ABCDE12345.com.example.app&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;ABCDE12345.com.example.app2&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;              &quot;components&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [ &lt;/span&gt;&lt;span style=&quot;color:#F44747&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    &quot;webcredentials&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      &quot;apps&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [ &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;ABCDE12345.com.example.app&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Because I was not sure where to find our &lt;strong&gt;Application Identifier Prefix&lt;/strong&gt;, I decided to watch the &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/717/&quot;&gt;&lt;strong&gt;WWDC 2019 What‘s New in Universal Links&lt;/strong&gt;&lt;/a&gt; video. It tells you:&lt;/p&gt;&lt;blockquote&gt;The prefix may or may not be equal to your Team Identifier. Check the developer portal to confirm your app identifier.&lt;/blockquote&gt;&lt;p&gt;In addition, I decided to check out &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2020/10098/&quot;&gt;&lt;strong&gt;WWDC 2020 What‘s new in Universal Links&lt;/strong&gt;&lt;/a&gt; to make sure I had the most up to date information. It tells you:&lt;/p&gt;&lt;blockquote&gt;The entitlement mentions your web server‘s domain name, and the web server mentions your app’s Application Identifier.&lt;/blockquote&gt;&lt;p&gt;At this point, I was confused at what the difference between your &lt;strong&gt;App Identifier&lt;/strong&gt; and &lt;strong&gt;Team Identifier&lt;/strong&gt; was and wasn‘t sure where to get the answer.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Team Identifier&lt;/strong&gt; is an identifier associated with your membership through the Apple developer program.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;App Identifier&lt;/strong&gt; is an identifier associated with the specific app on your developer account.&lt;/p&gt;&lt;p&gt;Following the first video, I went to the developer portal and found our &lt;strong&gt;Team Identifier&lt;/strong&gt; in the membership section.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9446bc14228b04a2f3359df1ff0976b17f970e32-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/9446bc14228b04a2f3359df1ff0976b17f970e32-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/9446bc14228b04a2f3359df1ff0976b17f970e32-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/9446bc14228b04a2f3359df1ff0976b17f970e32-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/9446bc14228b04a2f3359df1ff0976b17f970e32-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/9446bc14228b04a2f3359df1ff0976b17f970e32-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2282 2282w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9446bc14228b04a2f3359df1ff0976b17f970e32-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1384&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;I used it in our &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;apple-app-site-association&lt;/code&gt; file. The file was deployed and I went to the app and try out Password AutoFill and Universal Links requests. Nothing worked, yet the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;apple-app-site-association&lt;/code&gt; file passed validation. I was left wondering why.&lt;/p&gt;&lt;h3&gt;The Solution&lt;/h3&gt;&lt;p&gt;Hidden in the WWDC 2019 video I mentioned, they say:&lt;/p&gt;&lt;blockquote&gt;The prefix may or may not be equal to your Team Identifier.&lt;/blockquote&gt;&lt;p&gt;Depending on when the application you are working on was setup, your &lt;strong&gt;Team Identifier&lt;/strong&gt; and &lt;strong&gt;App Identifier&lt;/strong&gt; may or may not be the same. If your &lt;strong&gt;Team Identifier&lt;/strong&gt; and &lt;strong&gt;App Identifier&lt;/strong&gt; are not identical, the app will make Password AutoFill and Universal Links requests with the &lt;strong&gt;App Identifier&lt;/strong&gt; instead, resulting in the feature not working.&lt;/p&gt;&lt;p&gt;To validate what identifier you need to use, visit the &lt;a href=&quot;https://developer.apple.com/account/resources/identifiers/list&quot;&gt;&lt;strong&gt;Identifiers&lt;/strong&gt;&lt;/a&gt; tab of the &lt;strong&gt;Certificates, Identifiers &amp;amp; Profiles&lt;/strong&gt; section of the developer page for your application.&lt;/p&gt;&lt;p&gt;In the list, find your &lt;strong&gt;Bundle Identifier&lt;/strong&gt; and &lt;strong&gt;click on it&lt;/strong&gt; in the list.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b67b61803a30b544c86c3fff12810c15fbf5b1e7-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/b67b61803a30b544c86c3fff12810c15fbf5b1e7-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/b67b61803a30b544c86c3fff12810c15fbf5b1e7-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/b67b61803a30b544c86c3fff12810c15fbf5b1e7-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/b67b61803a30b544c86c3fff12810c15fbf5b1e7-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/b67b61803a30b544c86c3fff12810c15fbf5b1e7-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2282 2282w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b67b61803a30b544c86c3fff12810c15fbf5b1e7-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1384&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;I had no idea that you could click on these items, because there is no indication that the items are selectable.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;On this page, you will see &lt;strong&gt;App ID Prefix&lt;/strong&gt; which is the value you must use for the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;apple-app-site-association&lt;/code&gt; file. The prefix may or may not be equal to your &lt;strong&gt;Team Identifier&lt;/strong&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/2ee942b56c3dc935e08019f9099fa4a4f143cb9e-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/2ee942b56c3dc935e08019f9099fa4a4f143cb9e-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/2ee942b56c3dc935e08019f9099fa4a4f143cb9e-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/2ee942b56c3dc935e08019f9099fa4a4f143cb9e-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/2ee942b56c3dc935e08019f9099fa4a4f143cb9e-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/2ee942b56c3dc935e08019f9099fa4a4f143cb9e-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2282 2282w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/2ee942b56c3dc935e08019f9099fa4a4f143cb9e-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1384&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt; Example of App Identifier that is not the same as Team Identifier&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/544767b44291f6518fa9909767c4fd56feed5236-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/544767b44291f6518fa9909767c4fd56feed5236-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/544767b44291f6518fa9909767c4fd56feed5236-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/544767b44291f6518fa9909767c4fd56feed5236-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/544767b44291f6518fa9909767c4fd56feed5236-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/544767b44291f6518fa9909767c4fd56feed5236-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2282 2282w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/544767b44291f6518fa9909767c4fd56feed5236-2282x1974.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1384&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Example of App Identifier that is the same as Team Identifier&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Swap that out, deploy that to the web server, and test out Password AutoFill and Universal Links requests to see that they now work.&lt;/p&gt;&lt;h3&gt;How We Got There&lt;/h3&gt;&lt;p&gt;We came across this bug while working with a client. After a lot of validation and back and forth with our client‘s website team, we decided to look at the console logs the app was printing out when trying to make Password AutoFill requests. (&lt;strong&gt;Note:&lt;/strong&gt; you must test on device for this feature to work.)&lt;/p&gt;&lt;p&gt;Upon going to a screen where we expected Password AutoFill, we noticed at the time the console was printing out an error saying that it could not complete the Password AutoFill request.&lt;/p&gt;&lt;samp class=&quot;block bg-black text-white p-4 rounded-lg whitespace-nowrap overflow-x-scroll&quot;&gt; 2021-05-21 12:59:49.798251-0400 Authorization failed: Error Domain=AKAuthenticationError Code=-7089 \&amp;quot;(null)\&amp;quot; UserInfo={AKClientBundleID=\&amp;quot;BUNDLE IDENTIFIER HERE”} &lt;/samp&gt;&lt;p&gt;After discovering what I believe is a hidden page in the developer portal, we noticed that our app did not use the &lt;strong&gt;Team Identifier&lt;/strong&gt; as its app‘s identifier. We had our client’s website team update the file to include the proper &lt;strong&gt;App Identifier&lt;/strong&gt;, and now everything works!&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Interested in building an iOS app? We can help! &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Get in touch.&lt;/a&gt;&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Michael Amundsen</author></item><item><title>Scorecard is Here</title><link>https://lickability.com/blog/scorecard-is-here/</link><guid isPermaLink="true">https://lickability.com/blog/scorecard-is-here/</guid><description>Keep score with our new iOS app 🎉</description><pubDate>Mon, 03 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Our team at Lickability has been working on a new iOS app called Scorecard, and it‘s &lt;a href=&quot;https://apps.apple.com/app/id1441151208&quot;&gt;available on the App Store today&lt;/a&gt;!&lt;/p&gt;&lt;p&gt;Scorecard is an app that makes it easy to track scores in your favorite games. Just enter the name of the game, add players, and start playing! As points are earned, watch as the scoreboard updates instantly to show who’s winning. Do you love playing games with friends and family, but hate fiddling with tiny pencils, notebooks, or dice to keep score? Scorecard was designed just for you.&lt;/p&gt;&lt;p&gt;If you enjoy playing the same games periodically, Scorecard lets you start new sessions with a single tap. While you’re playing, you can group players into teams, start new rounds, total up your scores, and even AirPlay the scoreboard to your TV. Once you’re done, pick a winner and celebrate your victory by sharing the final tally with your friends on social media.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/956d2763c4a5907ae24b55730df8a27c313a6988-2024x1012.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/956d2763c4a5907ae24b55730df8a27c313a6988-2024x1012.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/956d2763c4a5907ae24b55730df8a27c313a6988-2024x1012.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/956d2763c4a5907ae24b55730df8a27c313a6988-2024x1012.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/956d2763c4a5907ae24b55730df8a27c313a6988-2024x1012.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/956d2763c4a5907ae24b55730df8a27c313a6988-2024x1012.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2024 2024w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/956d2763c4a5907ae24b55730df8a27c313a6988-2024x1012.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;800&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;More features we love&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Sync your games to iCloud so they’re available on all your devices&lt;/li&gt;&lt;li&gt;Match each player’s mood or game piece with a custom color&lt;/li&gt;&lt;li&gt;Tap a score to access a built-in calculator to do more complex math&lt;/li&gt;&lt;li&gt;Snap a photo or choose an avatar for everyone in your group&lt;/li&gt;&lt;li&gt;Score points highest-to-lowest or vice versa, depending on the game rules&lt;/li&gt;&lt;li&gt;Long press on the app icon to resume a recent game or start a new one&lt;/li&gt;&lt;li&gt;Play at any time of day or night, with full support for dark mode&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Thanks! ❤️&lt;/h3&gt;&lt;p&gt;We want to say a special thank you to our friends and beta testers for their support, and to &lt;a href=&quot;https://www.jeremys.be&quot;&gt;Jeremy Swinnen&lt;/a&gt; for designing Scorecard. We couldn‘t have made this without any of you.&lt;/p&gt;&lt;p&gt;If you want to give Scorecard a try, you can &lt;strong&gt;download it right &lt;a href=&quot;https://apps.apple.com/app/id1441151208&quot;&gt;here&lt;/a&gt;&lt;/strong&gt;. Once you play around with it a bit, we‘d love to know what you think! Leave us a review on the App Store or send us suggestions on &lt;a href=&quot;https://twitter.com/getscorecardapp&quot;&gt;Twitter&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;And if you want to learn even more about Scorecard, take a look at our &lt;a href=&quot;https://getscorecard.app&quot;&gt;website&lt;/a&gt; to see all the fun things you can do with it!&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Welcome to Club Mobile</title><link>https://lickability.com/blog/welcome-to-club-mobile/</link><guid isPermaLink="true">https://lickability.com/blog/welcome-to-club-mobile/</guid><description>Our new weekly audio show</description><pubDate>Thu, 01 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We’re huge fans of the audio medium at Lickability. Lots of folks at the company &lt;a href=&quot;https://lickability.com/blog/podcasts-we-can-t-live-without/&quot;&gt;listen to podcasts&lt;/a&gt; during work and in their free time as a way to relax, learn, and laugh without the additional stress of screens. And we’ve long pondered the idea of introducing a podcast of our own to talk about our thoughts on the design and development of great apps, but something was always missing: you!&lt;/p&gt;&lt;p&gt;At the end of February, we launched an experimental audio show on &lt;a href=&quot;https://www.joinclubhouse.com&quot;&gt;Clubhouse&lt;/a&gt;, a rapidly growing audio social network and an &lt;a href=&quot;https://lickability.com/clients&quot;&gt;app we’ve been lucky enough to work on&lt;/a&gt; a bit. Clubhouse is designed as a space for casual, drop-in audio conversations—with friends and other interesting people around the world. From even that first episode, the thing that immediately struck us was how fun it was to interact live with our audience, answer questions, and share our favorite tips and tricks with folks listening. Now it’s become a fixture of our week. Every Friday at 4 PM ET, we put on our AirPods and talk about the topics that are top of mind.&lt;/p&gt;&lt;p&gt;So far we’ve recommended apps as &lt;a href=&quot;https://www.joinclubhouse.com/event/M4GE3BrR&quot;&gt;software sommeliers&lt;/a&gt;, discussed &lt;a href=&quot;https://www.joinclubhouse.com/event/M6wWK0zm&quot;&gt;localization best practices&lt;/a&gt;, chatted about how to &lt;a href=&quot;https://www.joinclubhouse.com/event/PGAe7KVM?ref=clublink.to&quot;&gt;hire great engineers&lt;/a&gt;, and surveyed the landscape of &lt;a href=&quot;https://www.joinclubhouse.com/event/m307ak4r&quot;&gt;continuous integration services&lt;/a&gt;. In every single one of these live conversations, we’ve invited folks in the community to share the stage with us where you’ve taught us new things and helped us cover the topics in even more depth. And since all of these conversations are live and ephemeral, we can be totally honest about our feelings on certain technologies or problems without worrying about making sure they’re updated as the industry changes.&lt;/p&gt;&lt;p&gt;Our Clubhouse show has gone from an experiment to a great new way to connect with folks who are interested in &lt;a href=&quot;https://lickability.com/about&quot;&gt;working with us&lt;/a&gt; or &lt;a href=&quot;https://lickability.com/jobs&quot;&gt;working for us&lt;/a&gt;, and we’re excited to continue hosting great conversations with our community, so we’ve given it a permanent home as a Clubhouse club. It’s called &lt;a href=&quot;https://www.joinclubhouse.com/club/club-mobile&quot;&gt;&lt;strong&gt;Club Mobile&lt;/strong&gt;&lt;/a&gt;, and over 300 designers, developers, founders, and students from all over the world have become members and followers, to be notified each week when we go live.&lt;/p&gt;&lt;p&gt;This Friday, our engineers will be discussing the ins-and-outs of different &lt;a href=&quot;https://www.joinclubhouse.com/event/PvK2VQke&quot;&gt;iOS layout technologies&lt;/a&gt;, but we’re always thinking up new ideas for new topics to discuss. If you have anything you’d like to hear about or need an invite to Clubhouse, send us a message on Twitter at &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;@lickability&lt;/a&gt; and it may just turn into a live show sometime down the road. Welcome to the club! ✌️&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Jan 31, 2022: As of this update, Club Mobile is on an indefinite hiatus. We loved doing it and hope we can bring it back, but at the moment, it just doesn‘t fit into our schedules.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>mb bischoff</author></item><item><title>How To Learn Swift</title><link>https://lickability.com/blog/how-to-learn-swift/</link><guid isPermaLink="true">https://lickability.com/blog/how-to-learn-swift/</guid><description>A guide for beginners</description><pubDate>Tue, 16 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Learning a new programming language can be a very challenging concept to most if you haven‘t done it before. In today’s day and age, there are a plethora of resources that provide some level of training or practice. But where do you get started? Well, you‘ve come to the right place! The ideal reader for this post is someone who has heard all kinds of nice things about Swift and the Apple ecosystem and wants to get started learning the language. By the end of this post, you should have a structured approach to learning Swift while also being provided suggested tools and resources to assist in the process.&lt;/p&gt;&lt;p&gt;So, you have finally decided that you are ready to learn! Now what? In order to help make this post applicable to as many people as possible, I am going to detail how I learned Swift with the hopes that you will be able to pick and choose what works for you. The techniques listed can be applied to Swift or any other programming language.&lt;/p&gt;&lt;h3&gt;What‘s your learning style?&lt;/h3&gt;&lt;p&gt;When taking on the challenge of learning a new programming language, one of the first things you should do is establish or figure out your learning style. By investigating the way you learn best, you‘ll be able to fine-tune your learning and get the most out of it. If you’re like me, you might be picking up the new language while working a full-time job and time is of the essence. This means you need to be very pragmatic with your time and effort. I discovered early on that I am a very visual learner, so I focused my efforts on finding video content that taught the core concepts of Swift with examples. I came to this discovery because of how frustrated I was when I read docs and they didn‘t make sense to me. I was missing the ability to connect the dots. However, when I watched video content, I was able to pick up on other cues that helped me develop a connection between the theoretical and the practical use case.&lt;/p&gt;&lt;p&gt;If you are unsure about your learning style, there are several tools that can assist with this. While doing some research for this post I found the &lt;a href=&quot;https://www.how-to-study.com/learning-style-assessment/&quot;&gt;Learning Style Assessment&lt;/a&gt; and it confirmed for me everything I already knew about myself. I highly suggest completing the assessment if you are having trouble determining your learning style. Once complete, you will be presented with your learning style as well as suggestions for how you should study or consume content.&lt;/p&gt;&lt;h3&gt;Resources to learn from&lt;/h3&gt;&lt;p&gt;When I identified my learning style, I started on my journey to find resources that best complemented this style. My initial reasoning for learning Swift was to build impactful and useful apps. With that in mind, I started with project-based learning. One thing to be mindful of is there is a difference between learning Swift and learning iOS. On one hand, you are learning a programming language, on the other, you are learning how to use frameworks and libraries using that language. Over the past few years, Swift has become useful for things like scripting, server-side use, as well as IoT (Raspberry Pi, Arduino) use cases. When deciding which resources are best, keep in mind the end goal of how you would like to use Swift.&lt;/p&gt;&lt;p&gt;Below are some of the resources I‘ve used:&lt;/p&gt;&lt;h4&gt;&lt;a href=&quot;https://www.hackingwithswift.com/&quot;&gt;Hacking With Swift&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Hacking With Swift run by &lt;a href=&quot;https://twitter.com/twostraws&quot;&gt;Paul Hudson&lt;/a&gt; is one of the most comprehensive sites there is when it comes to learning swift. He has several books that are free and paid as well as the &lt;a href=&quot;https://www.hackingwithswift.com/100&quot;&gt;100 Days Of Swift&lt;/a&gt; challenge. What I like the most about the 100 Days of Swift are the corresponding quizzes. It‘s fairly easy to just read through something and think that you understand it, so taking a quiz on the subject ensures you’ve retained the knowledge.&lt;/p&gt;&lt;h4&gt;&lt;a href=&quot;https://www.weheartswift.com/variables-constants-basic-operations/&quot;&gt;We Heart Swift&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;I found this cool little tool when I was searching for ways to learn Swift and I found it very helpful because they have lessons and quizzes that you can take.&lt;/p&gt;&lt;h4&gt;&lt;a href=&quot;https://www.udemy.com&quot;&gt;Udemy&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;This is one of my favorite resources because of the variety and cost of courses provided. Although I had issues with the mobile app, it was one of the only apps with offline access to videos. This feature alone kept me on the platform because I could download the content and watch it on the train when I had little to no internet access. I used Udemy mostly for the project-based learning approach where I will find a course that has interesting projects but also has very good “Swift language” concepts as part of the course. Here are some of my criteria when deciding which course to purchase on Udemy:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What is the star rating and how many students have left ratings?&lt;/strong&gt; Regarding this, you have to be careful because it can be misleading. Some instructors have 4 or 5-star ratings but a very small sample size. If their sample size is small, be sure to read the actual text of the ratings to get a better idea of the quality of the course.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Evaluate the Table of Contents&lt;/strong&gt; Is this course going to teach me Swift, iOS, or both? Depending on where you are in your journey, you may have already learned the basics of Swift and are looking to supplement your learning by building actual projects. Some courses focus simply on projects while others include a section solely dedicated to learning the Swift programming language. As I stated above, there is a difference between learning Swift the programming language, and something like UIKit to create your user interface and such. If it‘s a comprehensive course, this will be called out in the outline or table of contents.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Use focused search terms&lt;/strong&gt; If you are looking for a course that is focused on teaching Swift, be sure to use that plus the version in your search term. At the time of writing this post, the current version of Swift is Swift 5.&lt;/p&gt;&lt;h4&gt;&lt;a href=&quot;https://www.youtube.com&quot;&gt;Youtube&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;I made heavy use of youtube during my early learning days and still do. My favorite aspect of Youtube are the sheer number of videos available and the recommendation engine that suggests certain videos after completing one. I discovered several new channels in this way. Below are a few of my favorites for folks that are new to iOS and Swift:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCv75sKQFFIenWHrprnrR9aA&quot;&gt;Kilo Loco&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCbTw29mcP12YlTt1EpUaVJw&quot;&gt;Sean Allen&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/user/CodeWithChris&quot;&gt;Code With Chris&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;&lt;a href=&quot;https://apps.apple.com/us/app/unwrap/id1440611372&quot;&gt;Unwrap&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;This is a cool open sourced app created by &lt;a href=&quot;https://twitter.com/twostraws&quot;&gt;Paul Hudson&lt;/a&gt; and is the perfect tool for learning on the go. I believe when learning a programming language you need to CODE EVERY DAY. Depending on your circumstance, it may not always be easy to sit in front of a computer. In those cases, apps like this one are really straightforward and give you a concise means of focusing on learning the Swift language. This app has video and text-based content with interactive quizzes and lessons.&lt;/p&gt;&lt;h4&gt;&lt;a href=&quot;https://apps.apple.com/us/app/mimo-learn-coding-programming/id1133960732&quot;&gt;Mimo&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;This is another one of my favorites when it comes to passively learn in those moments where you cannot be in front of a computer. The interesting part of this app is that it‘s not specific to Swift and has several other programming languages available to learn.&lt;/p&gt;&lt;h3&gt;The 5 sources method&lt;/h3&gt;&lt;p&gt;In the process of learning, I came across a methodology that still serves me well to this day. I learned about this in a roundabout way while purchasing several Udemy courses. I noticed a pattern that with each course I took, there were certain topics that an instructor would teach, that made absolutely no sense to me, but then I‘d visit another course, and it would be explained in such a way that made perfect sense. It was then that I realized that anytime I don’t understand something, it‘s not my fault, it’s the instructors‘ fault. What I mean by this is that your brain works the way it works, and your learning style is what it is. Unfortunately, everyone’s teaching style may not match your learning style. This is not a knock on the instructor per se, you just need another angle.&lt;/p&gt;&lt;p&gt;My solution to this problem was to always find 5 sources of competing information when studying a topic. These sources don‘t need to be from the same platform but you can do that if it is easier for you. For example, let’s say I‘m learning about &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;for&lt;/code&gt; loops, the goal would be to find 5 competing resources that teach &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;for&lt;/code&gt; loops and just that alone. What I would be searching for is how to make the information I‘ve found compete with each other. I would search for similarities in the teaching and what the core concepts that they each share. By doing that, I would have a much more well-rounded understanding of the subject at hand.&lt;/p&gt;&lt;h3&gt;Summary&lt;/h3&gt;&lt;p&gt;Learning a new programming language and can be fun and stressful at the same time. I found that what worked for me was to first figure out my learning style and then use the techniques mentioned above to really maximize my study time. As stated above, the key is to have a very regular study schedule and code every day. In order to achieve that, you need the right type of resources to fill in the gap. If you apply the 5 sources method, you‘ll have a multitude of resources that you can pick from to enhance your learning. In times where you are on-the-go, be sure to try out the mobile apps listed above to keep the knowledge fresh in your mind while testing how well you retain it.&lt;/p&gt; </content:encoded><author>Marc Aupont</author></item><item><title>Building a Customizable UITextField with Combine</title><link>https://lickability.com/blog/building-a-customizable-uitextfield-with-combine/</link><guid isPermaLink="true">https://lickability.com/blog/building-a-customizable-uitextfield-with-combine/</guid><description>Our guide to text field overlays, customizations, and more</description><pubDate>Tue, 09 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Pokemon style “A UITextField appears” animation&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6a2c64e31a4568163453045feb16c54b887e56e0-608x256.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=304 304w, https://cdn.sanity.io/images/nkt6o869/production/6a2c64e31a4568163453045feb16c54b887e56e0-608x256.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=608 608w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6a2c64e31a4568163453045feb16c54b887e56e0-608x256.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=608&quot; width=&quot;608&quot; height=&quot;256&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Customizing text fields is a critical feature of almost every mobile app. We often use them when we need a user to fill out text as part of a form. Because we use them so often, we’re always trying to find ways to optimize implementing them in our app’s user interface.&lt;/p&gt;&lt;p&gt;Here is an example of a customized text field that has additionally inset text and an icon on the right:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e708e1ba5a9c397b00f0525882cf7ff36517b514-1090x200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=273 273w, https://cdn.sanity.io/images/nkt6o869/production/e708e1ba5a9c397b00f0525882cf7ff36517b514-1090x200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=545 545w, https://cdn.sanity.io/images/nkt6o869/production/e708e1ba5a9c397b00f0525882cf7ff36517b514-1090x200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=818 818w, https://cdn.sanity.io/images/nkt6o869/production/e708e1ba5a9c397b00f0525882cf7ff36517b514-1090x200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1090 1090w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e708e1ba5a9c397b00f0525882cf7ff36517b514-1090x200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1090&quot; width=&quot;1090&quot; height=&quot;200&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Building a Customizable Text Field&lt;/h3&gt;&lt;p&gt;​ To help us build a similar text field that is customizable, we’ll start by subclassing &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uitextfield&quot;&gt;UITextField&lt;/a&gt;.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;We made an Xcode project with all of the examples shown in this post. You can find it right &lt;a href=&quot;https://github.com/Lickability/Customizable-TextField-Demo&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;  &lt;/aside&gt;&lt;p&gt;To achieve setting custom insets we‘ll need to override a few methods, specifically the bounds of the text field’s label. Overriding &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uitextfield/1619636-textrect&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;text​Rect(for​Bounds:)&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uitextfield/1619589-editingrect&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;editing​Rect(for​Bounds:)&lt;/code&gt;&lt;/a&gt; will yield the results we want:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// Used to inset the bounds of the text field. The x values is applied as horizontal insets and the y value is applied as vertical insets.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; centerInset: CGPoint = .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;zero&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    didSet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        setNeedsLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// MARK: - UITextField&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;override&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; textRect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forBounds&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; bounds&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: CGRect) -&gt; CGRect {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    insetTextRect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forBounds&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: bounds)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;override&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; editingRect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forBounds&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; bounds&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: CGRect) -&gt; CGRect {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    insetTextRect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forBounds&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: bounds)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; insetTextRect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forBounds&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; bounds&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: CGRect) -&gt; CGRect {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; insetBounds = bounds.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;insetBy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dx&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: centerInset.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: centerInset.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;y&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; insetBounds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f94ed081eb640e204f6fcc9b10ddc42592c00fdc-1102x199.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=276 276w, https://cdn.sanity.io/images/nkt6o869/production/f94ed081eb640e204f6fcc9b10ddc42592c00fdc-1102x199.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=551 551w, https://cdn.sanity.io/images/nkt6o869/production/f94ed081eb640e204f6fcc9b10ddc42592c00fdc-1102x199.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=827 827w, https://cdn.sanity.io/images/nkt6o869/production/f94ed081eb640e204f6fcc9b10ddc42592c00fdc-1102x199.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1102 1102w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f94ed081eb640e204f6fcc9b10ddc42592c00fdc-1102x199.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1102&quot; width=&quot;1102&quot; height=&quot;199&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;We talked about adding custom views in the text field as well. Since we modified the bounds of the text field‘s label we’ll need to consider the adjusted insets. Similarly to the overridden methods above, we‘ll need to override &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uitextfield/1619638-rightviewrect&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;right​View​Rect(for​Bounds:)&lt;/code&gt;&lt;/a&gt; like this:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// Sets padding for the `rightView` from the text field&apos;s edge.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightViewPadding: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    didSet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        setNeedsLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;override&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; rightViewRect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forBounds&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; bounds&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: CGRect) -&gt; CGRect {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightViewRect = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;super&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;rightViewRect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forBounds&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: bounds)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    rightViewRect.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;origin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -= rightViewPadding&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightViewRect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;The frame of the right view‘s rectangle is also adjusted to include some padding. Since we have a foundation set up for the right view all that is left is to populate it. &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Text​Field&lt;/code&gt; doesn‘t have a method like &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uibutton/1623997-setimage&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;set​Image&lt;/code&gt;&lt;/a&gt; to instantiate an image view for us like a &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uibutton&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;UI​Button&lt;/code&gt;&lt;/a&gt; would. Setting the right view with a &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;UI​Button&lt;/code&gt; will suffice. Here‘s what that would look like with some optional properties like text and tint color:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightAccessoryButton: UIButton = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UIButton&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;frame&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;zero&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// Sets padding for the `rightView` from the text field&apos;s edge.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightViewPadding: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    didSet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        setNeedsLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// Sets an image for the `rightView`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightViewImage: UIImage? {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    didSet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        rightAccessoryButton.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setImage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(rightViewImage, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;normal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        rightViewMode = rightViewImage != &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ? .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;always&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;never&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// Sets text for the `rightView`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightViewText: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    didSet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        rightAccessoryButton.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setTitle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(rightViewText, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;normal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        rightAccessoryButton.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setTitleColor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(rightViewTintColor, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;normal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        rightViewMode = rightViewText != &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ? .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;always&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;never&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// Sets tintColor for the `rightView`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightViewTintColor: UIColor? {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    didSet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightViewImage != &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            rightAccessoryButton.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;tintColor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = rightViewTintColor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightViewText != &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            rightAccessoryButton.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setTitleColor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(rightViewTintColor, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;normal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; commonInit&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    rightView = rightAccessoryButton&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    rightViewMode = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;never&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Setting the &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uitextfield/1619607-rightviewmode&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;right​View​Mode&lt;/code&gt;&lt;/a&gt; will notify the text field when the right view overlay should appear. We also need to update the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;inset​Text​Rect(for​Bounds:)&lt;/code&gt; with the additional right view padding and width of the right view:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; insetTextRect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forBounds&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; bounds&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: CGRect) -&gt; CGRect {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; insetBounds = bounds.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;insetBy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dx&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: centerInset.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: centerInset.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;y&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    insetBounds.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -= rightViewPadding + rightAccessoryButton.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;bounds&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;width&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; insetBounds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;h3&gt;Configuring the customized text field&lt;/h3&gt;&lt;p&gt;Applications typically use a standardized design pattern (such as MVP or MVVM), which mostly derive from conforming to the &lt;a href=&quot;https://en.wikipedia.org/wiki/Separation_of_concerns&quot;&gt;separation of concerns principle&lt;/a&gt;. How do we handle text field customization and responding to user initiated actions while separating logic?&lt;/p&gt;&lt;p&gt;The core of this subclass contains several properties. It looks like we can make use of a &lt;a href=&quot;https://lickability.com/blog/our-view-on-view-models/&quot;&gt;view model&lt;/a&gt; as the main source of configuration for the text field. So far we have the center inset and padding properties but we could benefit from adding a right view object along with the text field properties:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// The view model containing information necessary for configuring the display of the view. &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ViewModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  /// The properties that can be applied to the right view. &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  struct&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; RightView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    /// The optional image for the right view. &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; image: UIImage? &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    /// The optional text for the right view. &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; text: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    /// The tint color of the text and image. &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; tintColor: UIColor? &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    /// The padding between the accessory text and edge of the text field. &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; padding: CGFloat } &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  /// The text. &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; text: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  /// The color of the text. &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; textColor: UIColor? &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  /// The placeholder text. &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; placeholder: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  /// Sets the center inset of the text field. &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; centerInset: CGPoint &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  /// The optional `RightView` to display on the `rightView` of a `UITextField`. &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightView: RightView? } &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;This is also a perfect use case for taking advantage of Apple‘s &lt;a href=&quot;https://developer.apple.com/documentation/combine&quot;&gt;Combine&lt;/a&gt; framework. Pre-Combine, the text field responses could have been handled with closures or delegates. UIKit does not provide Combine support by default, so to simplify some of the following logic we‘ll use a 3rd party framework, &lt;a href=&quot;https://github.com/CombineCommunity/CombineCocoa&quot;&gt;CombineCocoa&lt;/a&gt;, for the UIKit publishers we need.&lt;/p&gt;&lt;h3&gt;Installing CombineCocoa with Swift Package Manager&lt;/h3&gt;&lt;p&gt;Adding a dependency via Apple‘s built in &lt;a href=&quot;https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app&quot;&gt;Swift Package Manager&lt;/a&gt; is quite easy. In Xcode, simply select &lt;strong&gt;File → Swift Packages → Add Package Dependency&lt;/strong&gt;. There, you will be prompted to enter a package repository url where you can add the &lt;a href=&quot;https://github.com/CombineCommunity/CombineCocoa&quot;&gt;CombineCocoa GitHub url&lt;/a&gt;. Selecting next will default to the latest release tag from the repository. After confirming the package options you should now see the dependency added in the project navigator:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/4b5b3af75fd81fcb18b143da6c42318eabd42d2d-430x96.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=215 215w, https://cdn.sanity.io/images/nkt6o869/production/4b5b3af75fd81fcb18b143da6c42318eabd42d2d-430x96.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=430 430w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/4b5b3af75fd81fcb18b143da6c42318eabd42d2d-430x96.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=430&quot; width=&quot;430&quot; height=&quot;96&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Adding Combine publishers&lt;/h3&gt;&lt;p&gt;We did a lot of customization with the text field subclass we created. Let‘s address event handling and responses to the right view button.&lt;/p&gt;&lt;p&gt;Since CombineCocoa is a wrapper around UIKit, there are built in publishers we can use. We‘ll focus on &lt;a href=&quot;https://github.com/CombineCommunity/CombineCocoa/blob/main/Sources/CombineCocoa/Controls/UITextField%2BCombine.swift&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;text​Publisher&lt;/code&gt;&lt;/a&gt; for text changes in the the text field and &lt;a href=&quot;https://github.com/CombineCommunity/CombineCocoa/blob/main/Sources/CombineCocoa/Controls/UIControl%2BCombine.swift&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;control​Event​Publisher(for:)&lt;/code&gt;&lt;/a&gt; for control events from the button.&lt;/p&gt;&lt;p&gt;These publishers can be added at initialization:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; cancellables = &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;AnyCancellable&gt;() &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; commonInit&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  rightView = rightAccessoryButton &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  rightViewMode = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;never&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  rightAccessoryButton.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;controlEventPublisher&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;primaryActionTriggered&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sink&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;         self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rightAccessoryButton&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isSelected&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toggle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  } &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;store&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &amp;#x26;cancellables) &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  textPublisher &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sink&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { text &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;         self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;viewModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = text &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        } &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;store&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &amp;#x26;cancellables) &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;} &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Since the publishers are exclusive to private properties we‘ll need another one for accessing the selected state of the button. CombineCocoa’s &lt;a href=&quot;https://github.com/CombineCommunity/CombineCocoa/blob/main/Sources/CombineCocoa/CombineControlProperty.swift&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Control​Property&lt;/code&gt;&lt;/a&gt; property publisher offers a very useful solution for this. We could initialize this generic publisher with a specific control event and key path:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// Called when the right accessory&apos;s enabled value has changed. &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightAccessoryButtonEnabled: AnyPublisher&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Bool&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, Never&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                                                            Publishers.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ControlProperty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;control&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: rightAccessoryButton, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;events&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;primaryActionTriggered&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;keyPath&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: \.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isSelected&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;eraseToAnyPublisher&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;} &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Now that we‘ve included the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;right​Accessory​Button&lt;/code&gt;‘s publisher we need to subscribe to the values it emits. In the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Demo​View​Controller&lt;/code&gt; this can be set up at &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;view​Did​Load()&lt;/code&gt;:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;override&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; viewDidLoad&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  super&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;viewDidLoad&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  customTextField.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rightAccessoryButtonEnabled&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sink&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { [&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;weak&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;] isSelected &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    guard&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    } &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;customTextField&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;viewModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rightView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;image&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = isSelected ? .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;unlocked&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;locked&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;customTextField&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isSecureTextEntry&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = !isSelected &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;} &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;store&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &amp;#x26;cancellables) &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;} &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a0778be09157560f79a2121ba36950940171b292-3840x1600.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/a0778be09157560f79a2121ba36950940171b292-3840x1600.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/a0778be09157560f79a2121ba36950940171b292-3840x1600.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/a0778be09157560f79a2121ba36950940171b292-3840x1600.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/a0778be09157560f79a2121ba36950940171b292-3840x1600.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/a0778be09157560f79a2121ba36950940171b292-3840x1600.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/a0778be09157560f79a2121ba36950940171b292-3840x1600.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/a0778be09157560f79a2121ba36950940171b292-3840x1600.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a0778be09157560f79a2121ba36950940171b292-3840x1600.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;667&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Adding @IBInspectable&lt;/h3&gt;&lt;p&gt;After all these additions to customize the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Text​Field&lt;/code&gt; subclass there is another option to extend these properties to Interface Builder. To better visualize this in Interface Builder, let‘s mark the properties as &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;@IB​Inspectable&lt;/code&gt;. This will allow us to edit those properties in the attributes inspector in Interface Builder. In code, that looks like this:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// Sets the center inset of the text field..&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@IBInspectable&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; centerInset: CGPoint = .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;zero&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    didSet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        setNeedsLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// Sets padding for the `rightView` from the text field&apos;s edge.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@IBInspectable&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightViewPadding: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    didSet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        setNeedsLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// Sets an image for the `rightView`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@IBInspectable&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightViewImage: UIImage? {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    didSet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        rightAccessoryButton.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setImage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(rightViewImage, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;normal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        rightViewMode = rightViewImage != &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; || rightViewText != &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ? .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;always&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;never&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// Sets text for the `rightView`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@IBInspectable&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightViewText: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    didSet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        rightAccessoryButton.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setTitle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(rightViewText, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;normal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        rightAccessoryButton.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setTitleColor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(rightViewTintColor, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;normal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        rightViewMode = rightViewText != &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; || rightViewImage != &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ? .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;always&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;never&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// Sets tintColor for the `rightView`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@IBInspectable&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightViewTintColor: UIColor? {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    didSet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightViewImage != &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            rightAccessoryButton.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;tintColor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = rightViewTintColor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rightViewText != &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            rightAccessoryButton.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setTitleColor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(rightViewTintColor, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;normal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;And in Interface Builder, it should look like this:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/02e44ecd04f4259859b58a043ade2228aa32a05b-638x142.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=319 319w, https://cdn.sanity.io/images/nkt6o869/production/02e44ecd04f4259859b58a043ade2228aa32a05b-638x142.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=638 638w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/02e44ecd04f4259859b58a043ade2228aa32a05b-638x142.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=638&quot; width=&quot;638&quot; height=&quot;142&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Since we have all the inspectables set up in Interface Builder, we can go a step further and have those values set up as the default view model, and then take a look at what the project‘s Interface Builder generates from the inspectables:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;override&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; awakeFromNib&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;awakeFromNib&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    viewModel = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ViewModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: text, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;textColor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: textColor, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;placeholder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: placeholder, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;centerInset&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: centerInset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;rightView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;image&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: rightViewImage, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: rightViewText, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tintColor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: rightViewTintColor, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;padding&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: rightViewPadding))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/cb74e449da0dcd9db8c4524ff3b0f6b26d22fe90-646x440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=323 323w, https://cdn.sanity.io/images/nkt6o869/production/cb74e449da0dcd9db8c4524ff3b0f6b26d22fe90-646x440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=646 646w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/cb74e449da0dcd9db8c4524ff3b0f6b26d22fe90-646x440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=646&quot; width=&quot;646&quot; height=&quot;440&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Results in Interface Builder&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Running the project without having modified the view controller will reveal that the properties have been set directly from the Interface Builder:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c9636e32587188ebbc8bbd7d918139d43dd5eec6-1123x234.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/c9636e32587188ebbc8bbd7d918139d43dd5eec6-1123x234.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=562 562w, https://cdn.sanity.io/images/nkt6o869/production/c9636e32587188ebbc8bbd7d918139d43dd5eec6-1123x234.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=842 842w, https://cdn.sanity.io/images/nkt6o869/production/c9636e32587188ebbc8bbd7d918139d43dd5eec6-1123x234.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1123 1123w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c9636e32587188ebbc8bbd7d918139d43dd5eec6-1123x234.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1123&quot; width=&quot;1123&quot; height=&quot;234&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Summary&lt;/h3&gt;&lt;p&gt;Being able to set up inspectable properties directly in Interface Builder allows for more flexibility and clarity when composing reusable views. The source of truth in the view model also allows us to set default properties. In this post, we learned how simple it is to set up a chain of Combine publishers. We also leveraged an older feature like &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;IB​Inspectable&lt;/code&gt; and paired it with a newer technology like Combine. We hope this provides useful insight on how to implement these reactive changes in your existing apps.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Are you building an iOS app? We want to help! &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Get in touch&lt;/a&gt;.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Daisy Ramos</author></item><item><title>The Atlantic App—A Mobile Revival</title><link>https://lickability.com/blog/the-atlantic-app-a-mobile-revival/</link><guid isPermaLink="true">https://lickability.com/blog/the-atlantic-app-a-mobile-revival/</guid><description>How a neglected app became the expected app for an American institution.</description><pubDate>Thu, 18 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;Everyone knows &lt;em&gt;The Atlantic&lt;/em&gt;&lt;/h3&gt;&lt;p&gt;It’s an American magazine &amp;amp; institution, founded in 1857 in Boston, Massachusetts. It started as &lt;em&gt;The Atlantic Monthly&lt;/em&gt;, a scholarly &amp;amp; cultural magazine of leading writers.&lt;/p&gt;&lt;p&gt;Thinkers like Ralph Waldo Emerson, Emily Dickinson, and Henry Wadsworth Longfellow—all expressing cultural commentaries on topics like the abolition of slavery, education and other major political issues of the day.&lt;/p&gt;&lt;p&gt;Fast forward to today…&lt;/p&gt;&lt;p&gt;The Atlantic still publishes opinions at &lt;a href=&quot;http://theatlantic.com/&quot;&gt;theatlantic.com&lt;/a&gt;. And, of course, daily coverage &amp;amp; analysis of breaking news, politics, education, technology, health, science, and culture.&lt;/p&gt;&lt;p&gt;But here’s the thing…&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f7deb8835223328dbaf4d35f2bc2765f8c25d36c-1344x756.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=336 336w, https://cdn.sanity.io/images/nkt6o869/production/f7deb8835223328dbaf4d35f2bc2765f8c25d36c-1344x756.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=672 672w, https://cdn.sanity.io/images/nkt6o869/production/f7deb8835223328dbaf4d35f2bc2765f8c25d36c-1344x756.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1008 1008w, https://cdn.sanity.io/images/nkt6o869/production/f7deb8835223328dbaf4d35f2bc2765f8c25d36c-1344x756.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1344 1344w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f7deb8835223328dbaf4d35f2bc2765f8c25d36c-1344x756.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1344&quot; width=&quot;1344&quot; height=&quot;756&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;They had an iOS app—in need of a facelift&lt;/h3&gt;&lt;p&gt;Like so many companies, The Atlantic had a mobile app in 2017. Billions of people in the world were using their smartphones everyday. So readers expected to stay informed—no matter where they’re at. The gym, The Village, while driving (uh-oh).&lt;/p&gt;&lt;p&gt;However, the Atlantic app had some issues.&lt;/p&gt;&lt;p&gt;Users had a &lt;strong&gt;tough time logging in&lt;/strong&gt;. &lt;strong&gt;Saved articles &lt;em&gt;vanished&lt;/em&gt;&lt;/strong&gt;—&lt;em&gt;poof&lt;/em&gt;. It &lt;strong&gt;lacked some key features&lt;/strong&gt;. Not enough &lt;strong&gt;downloads&lt;/strong&gt;. Too many &lt;strong&gt;poor reviews&lt;/strong&gt;. And &lt;strong&gt;support requests&lt;/strong&gt; piling up.&lt;/p&gt;&lt;p&gt;The UI looked squished on modern devices. This mobile design reflected poorly on the Atlantic’s prestigious brand.&lt;/p&gt;&lt;h3&gt;So why not just fix all that?&lt;/h3&gt;&lt;p&gt;Whoever developed it could certainly make everything right, right? Well…&lt;/p&gt;&lt;p&gt;The Atlantic hired a vendor to write their initial app. After a couple product iterations, that relationship deteriorated. And they didn’t have the &lt;strong&gt;in-house expertise&lt;/strong&gt; to perform updates themselves.&lt;/p&gt;&lt;p&gt;Plus, the app was written in Objective-C, a rapidly aging programming language. Even worse, The Atlantic didn’t own the source code. So the app became neglected. And lonely. With no tech-TLC to speak of.&lt;/p&gt;&lt;p&gt;So sad.&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;The Atlantic&amp;#x27;s new app icon&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/1687a59a43238bc1725e302efae42e059a6f37ec-888x892.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/1687a59a43238bc1725e302efae42e059a6f37ec-888x892.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/1687a59a43238bc1725e302efae42e059a6f37ec-888x892.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/1687a59a43238bc1725e302efae42e059a6f37ec-888x892.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/1687a59a43238bc1725e302efae42e059a6f37ec-888x892.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;402&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;The Atlantic reached out to Lickability&lt;/h3&gt;&lt;p&gt;The Atlantic knew of us from our work &amp;amp; success with The New Yorker Today app. So The Atlantic talked with, learned from, then hired us to solve their iOS woes.&lt;/p&gt;&lt;p&gt;First, we &lt;strong&gt;developed a plan&lt;/strong&gt;. We understood The Atlantic’s pains &amp;amp; needs. Created estimates for each feature. It became clear to all—The Atlantic needed a new iOS app to keep up with (and beat) the competition.&lt;/p&gt;&lt;p&gt;Next, we led the way to &lt;strong&gt;develop a structured process&lt;/strong&gt;. We set up weekly meetings, GitHub projects, and a Slack channel for ad-hoc conversations.&lt;/p&gt;&lt;p&gt;Then, two of our developers travelled from NYC to DC to &lt;strong&gt;sketch &amp;amp; define&lt;/strong&gt; the APIs over the next couple days. These APIs became the connective tissue between The Atlantic’s data and the new mobile app.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the new Atlantic app&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/141de7c8870e7ba553e001e59df80b5fc6deeb2f-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=331 331w, https://cdn.sanity.io/images/nkt6o869/production/141de7c8870e7ba553e001e59df80b5fc6deeb2f-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=663 663w, https://cdn.sanity.io/images/nkt6o869/production/141de7c8870e7ba553e001e59df80b5fc6deeb2f-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=994 994w, https://cdn.sanity.io/images/nkt6o869/production/141de7c8870e7ba553e001e59df80b5fc6deeb2f-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325 1325w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/141de7c8870e7ba553e001e59df80b5fc6deeb2f-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325&quot; width=&quot;1325&quot; height=&quot;2616&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the new Atlantic app&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/46d3c646838cbd2e9b1f74113e36b824661e7f04-1443x2867.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=361 361w, https://cdn.sanity.io/images/nkt6o869/production/46d3c646838cbd2e9b1f74113e36b824661e7f04-1443x2867.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=722 722w, https://cdn.sanity.io/images/nkt6o869/production/46d3c646838cbd2e9b1f74113e36b824661e7f04-1443x2867.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1082 1082w, https://cdn.sanity.io/images/nkt6o869/production/46d3c646838cbd2e9b1f74113e36b824661e7f04-1443x2867.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1443 1443w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/46d3c646838cbd2e9b1f74113e36b824661e7f04-1443x2867.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1443&quot; width=&quot;1443&quot; height=&quot;2867&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Lickability was swift to (strongly) suggest Swift&lt;/h3&gt;&lt;p&gt;And The Atlantic was quick to listen. Why the Swift programming language to rewrite the app? Because… Swift is easy to write. Runs fast. Crashes less. And, is widely known in the iOS development community.&lt;/p&gt;&lt;p&gt;All this empowered The Atlantic to never be locked in to a vendor to maintain their own app &amp;amp; code.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Other goals achieved:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A vendor with &lt;strong&gt;media experience&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;A &lt;strong&gt;native iOS app&lt;/strong&gt; that rendered beautifully on iPhone and iPad&lt;/li&gt;&lt;li&gt;Showcase their magazine &lt;strong&gt;issues, sections, and settings&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;Smooth process to &lt;strong&gt;login, subscribe, and link&lt;/strong&gt; subscriptions&lt;/li&gt;&lt;li&gt;Allows users to &lt;strong&gt;save articles&lt;/strong&gt; for later reading&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Wide accessibility&lt;/strong&gt;—including for people with dyslexia &amp;amp; users needing large type&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Push notifications&lt;/strong&gt; for new stories &amp;amp; issues, with deep-links into the app&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Increase revenue using subscriptions&lt;/strong&gt; instead of discreet purchases&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Be recognized as a great app&lt;/strong&gt; by getting great reviews&lt;/li&gt;&lt;li&gt;&lt;strong&gt;A partner they could trust&lt;/strong&gt; to add new features &amp;amp; fix existing problems&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;While building the app, we worked closely with The Atlantic. We showed &amp;amp; shared progress daily. Implemented continuous integration with BuddyBuild. Worked with the team to perform ongoing QA testing. Staged design reviews and visual QA with the design team. And beta-tested with existing users.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Two hands holding an iPad displaying covers of The Atlantic magazine in the new app.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b44e9bb56b38bc1dda532dd21ad28affe956798d-2000x1334.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/b44e9bb56b38bc1dda532dd21ad28affe956798d-2000x1334.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/b44e9bb56b38bc1dda532dd21ad28affe956798d-2000x1334.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/b44e9bb56b38bc1dda532dd21ad28affe956798d-2000x1334.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/b44e9bb56b38bc1dda532dd21ad28affe956798d-2000x1334.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b44e9bb56b38bc1dda532dd21ad28affe956798d-2000x1334.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1067&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;In The Atlantic’s own words, Lickability…&lt;/h3&gt;&lt;p&gt;Paid attention to the details. Was very proactive. Incorporated feedback while pushing back as needed. Cared as much as we did. Felt like a true partner.&lt;/p&gt;&lt;blockquote class=&quot;quote-block&quot;&gt; &lt;p class=&quot;quote-text&quot;&gt;Lickability delivered on time, offered proactive suggestions for improvement, and took pride in their work.&lt;/p&gt; &lt;cite class=&quot;quote-author&quot;&gt;Betsy Cole, Executive Director, The Atlantic&lt;/cite&gt; &lt;/blockquote&gt;&lt;h3&gt;By the numbers&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;App Store reviews average 4.7 stars with 1.5K ratings&lt;/li&gt;&lt;li&gt;#5 in the Magazines &amp;amp; Newspapers Category&lt;/li&gt;&lt;li&gt;#10 in News&lt;/li&gt;&lt;li&gt;#3 in News &amp;amp; Politics Magazines&lt;/li&gt;&lt;li&gt;#1 in Literary Magazines&lt;/li&gt;&lt;li&gt;Featured by Apple as App of the Day&lt;/li&gt;&lt;/ul&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/07646ff91b93574f8cf6be21d1b5f43a9b4571bf-671x328.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=336 336w, https://cdn.sanity.io/images/nkt6o869/production/07646ff91b93574f8cf6be21d1b5f43a9b4571bf-671x328.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=671 671w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/07646ff91b93574f8cf6be21d1b5f43a9b4571bf-671x328.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=671&quot; width=&quot;671&quot; height=&quot;328&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Need a makeover for your app?&lt;/h3&gt;&lt;p&gt;Like we did with The Atlantic app? &lt;a href=&quot;https://lickability.com/contact&quot;&gt;&lt;strong&gt;We’re here for you&lt;/strong&gt;&lt;/a&gt; and your business.&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Citizen—Improving Their App Building Skills</title><link>https://lickability.com/blog/citizen-improving-their-app-building-skills/</link><guid isPermaLink="true">https://lickability.com/blog/citizen-improving-their-app-building-skills/</guid><description>How a company learned to improve on the inside, by hiring from the outside</description><pubDate>Thu, 18 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;Citizen is on a mission&lt;/h3&gt;&lt;p&gt;It’s to make the world a safer place for everyone.&lt;/p&gt;&lt;p&gt;Citizen believes stronger communities are safer communities. Where people can access information quickly, share effortlessly, and connect easily. &lt;strong&gt;Where people can watch out for each other&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;How? With a smartphone app that brings public information to everyone’s attention—in real time.&lt;/p&gt;&lt;p&gt;So people can protect a neighbor, prevent a tragedy, and count on one another. To create a safer world for each other, with each other.&lt;/p&gt;&lt;p&gt;Not much to not like about that.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d9575893f79de5c131fdbee570e0f56473ac7779-1200x675.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w, https://cdn.sanity.io/images/nkt6o869/production/d9575893f79de5c131fdbee570e0f56473ac7779-1200x675.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/d9575893f79de5c131fdbee570e0f56473ac7779-1200x675.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=900 900w, https://cdn.sanity.io/images/nkt6o869/production/d9575893f79de5c131fdbee570e0f56473ac7779-1200x675.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d9575893f79de5c131fdbee570e0f56473ac7779-1200x675.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200&quot; width=&quot;1200&quot; height=&quot;675&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;They had an app, loads of downloads, loads of users&lt;/h3&gt;&lt;p&gt;All good stuff, right? Not so fast. With all that, growing pains show up, too.&lt;/p&gt;&lt;p&gt;As their user base grew, so did their engineering team. But Citizen was relatively new to the NYC tech scene. They didn’t have an established network to recruit senior candidates from.&lt;/p&gt;&lt;p&gt;They did a great job bringing in developers, but needed a &lt;strong&gt;Senior iOS engineer&lt;/strong&gt; to lead the way.&lt;/p&gt;&lt;p&gt;Otherwise, processes and practices would break down as the organization scaled. They had millions of users—and wanted millions more.&lt;/p&gt;&lt;p&gt;They struggled with &lt;strong&gt;code reviews&lt;/strong&gt;. Too many &lt;strong&gt;bugs got stuck&lt;/strong&gt; instead of squashed. &lt;strong&gt;Features piled up&lt;/strong&gt; for the dev team. They launched &lt;strong&gt;too few features&lt;/strong&gt; in each release. The code base was in danger of &lt;strong&gt;becoming spaghetti-like&lt;/strong&gt;. And, they &lt;strong&gt;lacked the experience&lt;/strong&gt; to make the most of in-app purchases.&lt;/p&gt;&lt;p&gt;Citizen needed a pro to help streamline their engineering processes. They looked and looked for their hero for several months. As mentioned, it’s harder (and costs more) to hire senior talent.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/5e880e6a492057fed375962323830bbe3fec2198-2000x1174.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/5e880e6a492057fed375962323830bbe3fec2198-2000x1174.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/5e880e6a492057fed375962323830bbe3fec2198-2000x1174.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/5e880e6a492057fed375962323830bbe3fec2198-2000x1174.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/5e880e6a492057fed375962323830bbe3fec2198-2000x1174.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/5e880e6a492057fed375962323830bbe3fec2198-2000x1174.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;939&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;They found Lickability&lt;/h3&gt;&lt;p&gt;“Why look any longer?” “Why pay so much?” “Why not learn from the best?” Okay, maybe we got a little excited about that last one. But these were the thoughts of the leadership at Citizen.&lt;/p&gt;&lt;p&gt;We worked with Citizen to understand their pains, their needs, and their vision. Together we determined they could use a nudge to keep up with the pace of their users’ excitement, too.&lt;/p&gt;&lt;p&gt;More features released faster. More bugs fixed sooner. More people working better together. With a leader to augment their existing pool of quality talent.&lt;/p&gt;&lt;h3&gt;Say hey to Andrew—Partner at Lickability&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/aeb4020d86f4a680cff1e15dcd623023c1e51521-1024x683.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=256 256w, https://cdn.sanity.io/images/nkt6o869/production/aeb4020d86f4a680cff1e15dcd623023c1e51521-1024x683.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=512 512w, https://cdn.sanity.io/images/nkt6o869/production/aeb4020d86f4a680cff1e15dcd623023c1e51521-1024x683.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=768 768w, https://cdn.sanity.io/images/nkt6o869/production/aeb4020d86f4a680cff1e15dcd623023c1e51521-1024x683.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1024 1024w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/aeb4020d86f4a680cff1e15dcd623023c1e51521-1024x683.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1024&quot; width=&quot;1024&quot; height=&quot;683&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;We build apps all day, every day. We’ve seen and solved these same problems, over and over. Part of our business is to help others do the same for their business and apps.&lt;/p&gt;&lt;p&gt;It made sense for Citizen to work with Andrew. Why?&lt;/p&gt;&lt;p&gt;He had the &lt;strong&gt;biz and tech experience&lt;/strong&gt; for leading and advising. Required little time to ramp up and &lt;strong&gt;fit right in&lt;/strong&gt;. Had the mindset to &lt;strong&gt;lead by example&lt;/strong&gt;, not by authority. And…Citizen could &lt;strong&gt;save big $$$&lt;/strong&gt; over the long term by contracting vs. hiring.&lt;/p&gt;&lt;p&gt;Then, after 11 months, they could remove this charge from the books, while keeping the lessons learned—forever.&lt;/p&gt;&lt;h3&gt;Eleven months later—from ‘arg’ to ‘ahhh’&lt;/h3&gt;&lt;p&gt;Developing software is hard…and expensive. It. Just. Is. Even more so when your business can’t keep up with all the features your users want (and are willing to pay for).&lt;/p&gt;&lt;p&gt;We helped Citizen by sharing and showing a set of repeatable processes to carry on their already great work:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Refined their engineering practices&lt;/strong&gt; by learning from the outside&lt;/li&gt;&lt;li&gt;Enhanced their expertise to &lt;strong&gt;build features more quickly&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;Unclogged their pipeline to &lt;strong&gt;ship more features per week&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;Improved processes to &lt;strong&gt;capture, fix, and release bugs quicker&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Built rapport with the team&lt;/strong&gt; by balancing on-site vs. remote work&lt;/li&gt;&lt;li&gt;Showed slick ways for &lt;strong&gt;design to hand off assets to engineering&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Set up continuous integration&lt;/strong&gt; to test new features on all company phones&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Documented how to sustain&lt;/strong&gt; their new improvements&lt;/li&gt;&lt;/ul&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/92b63ff596ddc11f5320c460e97debe5d813302b-1024x683.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=256 256w, https://cdn.sanity.io/images/nkt6o869/production/92b63ff596ddc11f5320c460e97debe5d813302b-1024x683.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=512 512w, https://cdn.sanity.io/images/nkt6o869/production/92b63ff596ddc11f5320c460e97debe5d813302b-1024x683.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=768 768w, https://cdn.sanity.io/images/nkt6o869/production/92b63ff596ddc11f5320c460e97debe5d813302b-1024x683.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1024 1024w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/92b63ff596ddc11f5320c460e97debe5d813302b-1024x683.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1024&quot; width=&quot;1024&quot; height=&quot;683&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Check out a few of the features we helped deliver:&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Emoji Reaction Palette&lt;/strong&gt; with beautiful animations&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Magic Moments&lt;/strong&gt; to creatively show and highlight top news items&lt;/li&gt;&lt;li&gt;Re-wrote &lt;strong&gt;Follow Locations&lt;/strong&gt; feature to know what’s going on nearby&lt;/li&gt;&lt;li&gt;UI and backend to accept &lt;strong&gt;payments for subscriptions&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Citywide Incidents&lt;/strong&gt; for users to be briefed on events—instantly&lt;/li&gt;&lt;li&gt;&lt;strong&gt;News tab&lt;/strong&gt;—the first things users see for their city&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Share an Incident&lt;/strong&gt; to keep others safe from fires, gunmen, and arriving aliens&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Andrew rode off into the tech-sunset…&lt;/h3&gt;&lt;p&gt;Feeling good about leaving Citizen in better shape. With new ways to… review code. Squash bugs. Plan, build, and release code. Hire better. Ship faster. And integrate processes and people to live more peacefully at work. And more prosperously in their business.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/faf94b74c61550c8d967c0c23d2a2d60c6382f71-1600x900.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/faf94b74c61550c8d967c0c23d2a2d60c6382f71-1600x900.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/faf94b74c61550c8d967c0c23d2a2d60c6382f71-1600x900.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/faf94b74c61550c8d967c0c23d2a2d60c6382f71-1600x900.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/faf94b74c61550c8d967c0c23d2a2d60c6382f71-1600x900.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;900&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;In Citizen’s own words, Lickability…&lt;/h3&gt;&lt;blockquote class=&quot;quote-block&quot;&gt; &lt;p class=&quot;quote-text&quot;&gt;“We’re super pumped about Andrew&amp;#39;s work and our success. We became more focused and efficient. So much so, we were able to develop these iOS features in an Android app. Thanks, Andrew, for your patience, attention, experience and skills to help us be better and go faster.”&lt;/p&gt; &lt;cite class=&quot;quote-author&quot;&gt;Wiktor Macura ∙ Head of Engineering at Citizen&lt;/cite&gt; &lt;/blockquote&gt;&lt;h3&gt;By the numbers&lt;/h3&gt;&lt;p&gt;130K app reviews&lt;/p&gt;&lt;p&gt;4.8 stars average rating&lt;/p&gt;&lt;p&gt;#4 Overall&lt;/p&gt;&lt;p&gt;#1 in News&lt;/p&gt;&lt;p&gt;2+ billion alerts&lt;/p&gt;&lt;p&gt;5+ million users&lt;/p&gt;&lt;p&gt;20 cities &amp;amp; counties&lt;/p&gt;&lt;h3&gt;Want to build up your in-house, mobile dev skills?&lt;/h3&gt;&lt;p&gt;Like we did with Citizen? Got an app? Got a team? &lt;a href=&quot;https://lickability.com/contact&quot;&gt;&lt;strong&gt;We can help&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Dynamic Type &amp; In-App Font Scaling</title><link>https://lickability.com/blog/dynamic-type-and-in-app-font-scaling/</link><guid isPermaLink="true">https://lickability.com/blog/dynamic-type-and-in-app-font-scaling/</guid><description>Our guide to supporting custom fonts &amp; accessibility </description><pubDate>Thu, 11 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote class=&quot;quote-block&quot;&gt; &lt;p class=&quot;quote-text&quot;&gt;The Dynamic Type feature allows users to choose the size of textual content displayed on the screen. It helps users who need larger text for better readability. It also accommodates those who can read smaller text, allowing more information to appear on the screen. Apps that support Dynamic Type also provide a more consistent reading experience.&lt;/p&gt; &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uifont/scaling_fonts_automatically&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; &lt;cite class=&quot;quote-author&quot;&gt;Apple&amp;#39;s Developer Documentation&lt;/cite&gt; &lt;/a&gt; &lt;/blockquote&gt;&lt;p&gt;Since iOS 7, Apple has provided users with the ability to adjust the size of displayed content in your apps. Most content-driven apps support this feature seamlessly. This provides great support for every reader, but what if you want to provide extended scaling? What occurs behind the scenes, and what caveats (if any) are presented? You’re probably here because you need to support Dynamic Type or possibly build your own font scaling system, and we can help you.&lt;/p&gt;&lt;p&gt;First, let‘s take a look at the weight, size, and leading values for each text style in the default content category size (Large). The text style will determine the scale factor needed to support Dynamic Type.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A graph showing weight, size, and leading values for each text style in Apple&amp;#x27;s Human Interface Guidelines on Typography&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f73a66af6e7076257ca3a3178b2b391ee24a23fe-1526x1008.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=382 382w, https://cdn.sanity.io/images/nkt6o869/production/f73a66af6e7076257ca3a3178b2b391ee24a23fe-1526x1008.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=763 763w, https://cdn.sanity.io/images/nkt6o869/production/f73a66af6e7076257ca3a3178b2b391ee24a23fe-1526x1008.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1145 1145w, https://cdn.sanity.io/images/nkt6o869/production/f73a66af6e7076257ca3a3178b2b391ee24a23fe-1526x1008.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1526 1526w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f73a66af6e7076257ca3a3178b2b391ee24a23fe-1526x1008.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1526&quot; width=&quot;1526&quot; height=&quot;1008&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Weight, size, and leading values for each text style at the default Dynamic Type size from &lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/typography&quot;&gt;Apple’s Human Interface Guidelines on Typography&lt;/a&gt;&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Next, let‘s take a look at the Dynamic Type settings in iOS Settings, which can be found either under Accessibility or Display &amp;amp; Brightness. The “Larger Text” setting below is in &lt;strong&gt;Accessibility → Display &amp;amp; Text Size → Larger Text&lt;/strong&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot from the iOS Settings app showing the &amp;quot;Larger Accessibility Sizes&amp;quot; option in the &amp;quot;Larger Text&amp;quot; menu turned on.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d1ddd6741a4aa3743dd78bb36a0f51b2d1ebcd29-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/d1ddd6741a4aa3743dd78bb36a0f51b2d1ebcd29-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/d1ddd6741a4aa3743dd78bb36a0f51b2d1ebcd29-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/d1ddd6741a4aa3743dd78bb36a0f51b2d1ebcd29-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d1ddd6741a4aa3743dd78bb36a0f51b2d1ebcd29-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;2436&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Setting up Dynamic Type&lt;/h3&gt;&lt;p&gt;Luckily, in order to support Dynamic Type with system fonts, all we need are a few lines:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; textLabel = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UILabel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;textLabel.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;font&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;preferredFont&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forTextStyle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;headline&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;textLabel.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;adjustsFontForContentSizeCategory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;We made an Xcode project with all of the examples shown in this post. You can find it right &lt;a href=&quot;https://github.com/Lickability/custom-font-scaling&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;  &lt;/aside&gt;&lt;p&gt;There‘s also an option to set the “automatically adjusts font” flag in Interface Builder.&lt;/p&gt;&lt;blockquote class=&quot;quote-block&quot;&gt; &lt;p class=&quot;quote-text&quot;&gt;In Interface Builder, the Dynamic Type option to automatically adjust fonts applies only to text styles or scaled fonts returned by UIFontMetrics. It has no effect on custom fonts set in Interface Builder.&lt;/p&gt; &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uifont/scaling_fonts_automatically&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; &lt;cite class=&quot;quote-author&quot;&gt;Apple&amp;#39;s Developer Documentation&lt;/cite&gt; &lt;/a&gt; &lt;/blockquote&gt;&lt;p&gt;Notice how we didn’t set a custom font on the label but instead relied on the system font. There isn‘t much that is required to support the built-in system scaling.&lt;/p&gt;&lt;p&gt;As we saw above in the default content category size, the text styles are scaled at different sizes. In some cases you may want to provide font sizes that aren‘t listed. How can we support Dynamic Type if we want to use a custom font and size in our app?&lt;/p&gt;&lt;h3&gt;Custom Font Scaling with &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Font​Metrics&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;So, how can we guarantee our font size will be met with the Dynamic Type requirements? In order to observe changes, we will need to subscribe to &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicontentsizecategory/1622948-didchangenotification&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;UI​Content​Size​Category.did​Change​Notification&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;This sounds simple enough, but what if we have multiple screens to observe? It doesn’t seem very optimal to register for the same notification on each view controller. If your app is primarily navigation controller-based, a way around this would be to subclass &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uinavigationcontroller&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;UI​Navigation​Controller&lt;/code&gt;&lt;/a&gt;. We would simply iterate through the array of child view controllers (and their children) to set the &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiapplication/1623048-preferredcontentsizecategory&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;preferred​Content​Size​Category&lt;/code&gt;&lt;/a&gt; and override the trait collection to scale our custom font. Our notification to observe dynamic font size changes would look like this:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;NotificationCenter.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;addObserver&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;selector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;#selector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(overrideChildrenContentSizeCategories), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: UIContentSizeCategory.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;didChangeNotification&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;The key here is to override the view controller‘s trait collection with the correct &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Content​Size​Category&lt;/code&gt; whether it‘s user selected or the current &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;preferred​Content​Size​Category&lt;/code&gt;. That looks something like this:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Overrides the font cateogry to be used.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;override&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; addChild&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; childController&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: UIViewController) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;addChild&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(childController)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    overrideContentSizeCategory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(childController)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; overrideContentSizeCategory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; child&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: UIViewController) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   // Local storage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;   let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; preferences = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Preferences&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;   let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; contentSizeCategory: UIContentSizeCategory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   // Whether to use the user-selected content size category or the system one.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;   if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; preferences.shouldUseUserSelectedContentSizeCategory, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; userSelectedContentSizeCategory = preferences.userSelectedContentSizeCategory {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;       contentSizeCategory = userSelectedContentSizeCategory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;   } &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;       contentSizeCategory = UITraitCollection.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;current&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;preferredContentSizeCategory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;   }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   // The setting to scale the font.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;   let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; traitCollection = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UITraitCollection&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;preferredContentSizeCategory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: contentSizeCategory)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;   setOverrideTraitCollection&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(traitCollection, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forChild&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: child)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;All that‘s left is to apply the font as a type of &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Font​Metrics&lt;/code&gt;.&lt;/p&gt;&lt;blockquote class=&quot;quote-block&quot;&gt; &lt;p class=&quot;quote-text&quot;&gt;If you use a custom font in your app and want to let the user control the text size, you must create a scaled instance of the font in your source code. Call `scaledFont(for:)`, passing in a reference to the custom font that&amp;#39;s at a point size suitable for use with `large`. This is the default value for the Dynamic Type setting. You can use this call on the default font metrics, or you can specify a text style, such as `headline`.&lt;/p&gt; &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uifont/scaling_fonts_automatically&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; &lt;cite class=&quot;quote-author&quot;&gt;Apple&amp;#39;s Developer Documentation&lt;/cite&gt; &lt;/a&gt; &lt;/blockquote&gt;&lt;p&gt;Applying this to our label above would look like this:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; customFont = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UIFont&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Roboto-Italic&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;17&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    textLabel?.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;font&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UIFontMetrics&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forTextStyle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;headline&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;scaledFont&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: customFont)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Let‘s see what this looks like for a standard &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uilabel&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;UI​Label&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Demo of text scaling up and down.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/5ec9ae544ca2faafc20500c10e15db2f8c5f5141-600x1298.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w, https://cdn.sanity.io/images/nkt6o869/production/5ec9ae544ca2faafc20500c10e15db2f8c5f5141-600x1298.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/5ec9ae544ca2faafc20500c10e15db2f8c5f5141-600x1298.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600&quot; width=&quot;600&quot; height=&quot;1298&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;This is all that‘s needed to scale custom fonts with Dynamic Type. Sometimes in content-driven apps there is a need for web technologies for complex layouts / styles. Let’s see if we can support Dynamic Type and web-driven content via &lt;a href=&quot;https://developer.apple.com/documentation/webkit/wkwebview&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;WK​Web​View&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Dynamic Type and &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;WK​Web​View&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Sometimes apps need to display HTML content in a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;WK​Web​View&lt;/code&gt;. What do we need to do to make sure the typography in the web content can scale with Dynamic Type? Let‘s add an HTML and CSS stylesheet snippet like this:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;HTML&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;        &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; charset&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;        &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;viewport&quot;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; content&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;width=device-width, initial-scale=1, maximum-scale=1&quot;&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;        &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;link&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; rel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; href&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;stylesheet.css&quot;&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;h4&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Hi, we’re Lickability: a software studio in NYC that builds apps for clients like Jet, The Atlantic, Meetup, and more.&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;h4&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;We combine expert engineering with a touch of magic. The result? Beautiful, high-quality apps our clients (and their customers) love. ❤️&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;CSS&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;@font-face&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    font-family&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&apos;RobotoMono&apos;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    src&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&apos;RobotoMono-Regular.ttf&apos;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    font-style&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;normal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    font-weight&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;normal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    font-family&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&apos;RobotoMono&apos;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Unfortunately, this doesn‘t work unless you specify an Apple system font like &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;-apple-system-body&lt;/code&gt;. But we want the web view styled with the one in the HTML file. If we take what we learned above and apply that to our HTML styling it should look something like this:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; reloadWebView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Setting a placeholder font since the font is loaded through CSS. The font now relies on the system to scale our custom font after calling `scaledFont`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; scaledFont = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UIFontMetrics&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forTextStyle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;scaledFont&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;preferredFont&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forTextStyle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;compatibleWith&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: traitCollection)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    guard&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; localHTMLURL = Bundle.main.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forResource&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;example&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;withExtension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;html&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;         let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; htmlString = &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;contentsOf&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: localHTMLURL) &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;         return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // A quick way to style html without modifying the css stylesheet.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    loadHTML&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;withFont&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: scaledFont, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;htmlString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: htmlString)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loadHTML&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;withFont&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; font&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: UIFont, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;htmlString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; fontSetting = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&amp;#x3C;span style=&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;font-size: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;font.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pointSize&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&amp;#x3C;/span&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    webView.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;loadHTMLString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(fontSetting + htmlString, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;baseURL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: Bundle.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;bundleURL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Since we‘re using a web view we need to override &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uitraitenvironment/1623516-traitcollectiondidchange&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;trait​Collection​Did​Change(_:)&lt;/code&gt;&lt;/a&gt; since the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;UI​Content​Size​Category&lt;/code&gt; has changed and call &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;reload​Web​View()&lt;/code&gt; there.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;override&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; traitCollectionDidChange&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; previousTraitCollection&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: UITraitCollection?) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;   super&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;traitCollectionDidChange&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(previousTraitCollection)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;   reloadWebView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;We’ll see this example in action in the next section when we learn about environment overrides.&lt;/p&gt;&lt;h3&gt;Environment Overrides&lt;/h3&gt;&lt;p&gt;Luckily we can debug this without leaving the simulator. You can find that in Xcode via the &lt;strong&gt;Debug → View Debugging → Configure Environment Overrides&lt;/strong&gt;. There you will see a switch to toggle text and a Dynamic Type slider to adjust the font. This is what that looks like when running the example app.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;The Dynamic Type text slider in Xcode&amp;#x27;s Environment Overrides menu.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9abb71fb1f6a1cfd6703bdc08ee3f2336aba5782-724x680.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=362 362w, https://cdn.sanity.io/images/nkt6o869/production/9abb71fb1f6a1cfd6703bdc08ee3f2336aba5782-724x680.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=724 724w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9abb71fb1f6a1cfd6703bdc08ee3f2336aba5782-724x680.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=724&quot; width=&quot;724&quot; height=&quot;680&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;We observed how Dynamic Type works for native and web-based UI components. We also learned how &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Font​Metrics&lt;/code&gt; offloads some of the work needed to scale custom fonts. Maybe one day we‘ll see built-in custom font support for web views, but for now, &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;UI​Font​Metrics&lt;/code&gt; is a viable solution for font scaling.&lt;/p&gt; </content:encoded><author>Daisy Ramos</author></item><item><title>Aloe Bud—A Gentler, Kinder App</title><link>https://lickability.com/blog/aloe-bud-a-gentler-kinder-app/</link><guid isPermaLink="true">https://lickability.com/blog/aloe-bud-a-gentler-kinder-app/</guid><description>How an entrepreneur built a self-care app that met their needs—and how it helped others, too.</description><pubDate>Thu, 28 Jan 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;Say hey to Serenity Discko&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b72646503d06e4fb2f559649318c1e15f141fb1b-2400x1800.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/b72646503d06e4fb2f559649318c1e15f141fb1b-2400x1800.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/b72646503d06e4fb2f559649318c1e15f141fb1b-2400x1800.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/b72646503d06e4fb2f559649318c1e15f141fb1b-2400x1800.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/b72646503d06e4fb2f559649318c1e15f141fb1b-2400x1800.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/b72646503d06e4fb2f559649318c1e15f141fb1b-2400x1800.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b72646503d06e4fb2f559649318c1e15f141fb1b-2400x1800.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Photo by Heather Sten from &lt;a href=&quot;https://www.wired.com/story/amber-discko-aloe-bud/&quot;&gt;Wired&lt;/a&gt;.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Serenity (they/them) has been a social media professional for years. As an employee, a consultant, and now… as an entrepreneur. Back in 2016, they also did social media and voter registration work for Hillary Clinton.&lt;/p&gt;&lt;p&gt;That’s when everything changed.&lt;/p&gt;&lt;p&gt;In their own words, “While working on the campaign, time management took a back-seat to work, and things like remembering to brush my teeth or drink water became really difficult.” Life got tough. Leaving the house, staying hydrated, even eating a full meal felt like a battle. “My emotional and physical wellness hit an all-time-low,” they said.&lt;/p&gt;&lt;p&gt;They knew others felt the same—with their own struggles, &lt;strong&gt;just getting through the day&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;So they got to work.&lt;/p&gt;&lt;h3&gt;Serenity needed a better self-care app&lt;/h3&gt;&lt;p&gt;Sure, mediation apps like Headspace and Calm were gaining buzz in this space in 2017. But these apps, and other popular options, required &lt;strong&gt;expensive subscriptions&lt;/strong&gt; and were too &lt;strong&gt;medically focused&lt;/strong&gt;. That whole clinical vibe was impersonal and b-o-r-i-n-g.&lt;/p&gt;&lt;p&gt;There were other health-reminder apps but they sounded like &lt;strong&gt;mean, nagging robots&lt;/strong&gt;. Who needs to be beaten down for reminders to brush your teeth? To breathe? Or to check in with a loved one?&lt;/p&gt;&lt;p&gt;Nope, Serenity wanted an app that was free to use. And personable. Something that &lt;strong&gt;nurtured support for people struggling&lt;/strong&gt; with mental health challenges.&lt;/p&gt;&lt;h3&gt;But it cost big $$$ to build an app&lt;/h3&gt;&lt;p&gt;And they surely didn’t want to rely on the &lt;em&gt;build-it-and-they-will-come&lt;/em&gt; approach. That’s fantasy. Plus, where would they get the money?&lt;/p&gt;&lt;p&gt;First off, Serenity created a &lt;a href=&quot;https://twitter.com/aloebud&quot;&gt;Twitter bot&lt;/a&gt; as an MVP approach—to gain and gauge interest. People liked the idea and responded in droves. Clearly, lots of folks wanted self-care reminders that lived on their phones.&lt;/p&gt;&lt;p&gt;So then, maybe it was time to &lt;strong&gt;kick off a Kickstarter campaign&lt;/strong&gt;? Hmmm, but how much money should they raise? And then what do they do with it? They had no experience building apps. Ah, but wait…&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d40d3fe1c37d93c319bb5338a34830c95fe1ce3c-2048x1152.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/d40d3fe1c37d93c319bb5338a34830c95fe1ce3c-2048x1152.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/d40d3fe1c37d93c319bb5338a34830c95fe1ce3c-2048x1152.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/d40d3fe1c37d93c319bb5338a34830c95fe1ce3c-2048x1152.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/d40d3fe1c37d93c319bb5338a34830c95fe1ce3c-2048x1152.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/d40d3fe1c37d93c319bb5338a34830c95fe1ce3c-2048x1152.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2048 2048w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d40d3fe1c37d93c319bb5338a34830c95fe1ce3c-2048x1152.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;900&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Serenity contacted their friends at Lickability&lt;/h3&gt;&lt;p&gt;We had worked together years back at Tumblr. Serenity came to us with excitement…and questions.&lt;/p&gt;&lt;p&gt;How much will it cost to build an app? How long will it take? How will they generate revenue?&lt;/p&gt;&lt;p&gt;We answered all that—because &lt;strong&gt;we’re product experts, too&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;Voilà—a successful $50K &lt;a href=&quot;https://www.kickstarter.com/projects/aloe/aloe-app-gentle-self-care-reminders-from-yourself?ref=discovery&amp;term=aloe%20bud&quot;&gt;Kickstarter campaign&lt;/a&gt; to build a new app. And the name Aloe Bud was born—&lt;strong&gt;Aloe&lt;/strong&gt; for its medicinal properties, and &lt;strong&gt;Bud&lt;/strong&gt; to express something on its way to full bloom. And, of course, an app that will be your bud.&lt;/p&gt;&lt;p&gt;This app would encourage users to stay on top of their daily routines—gently. Whether reminding them to shower. To meditate. Or take their medication.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/46b102414d13eccddfdfac7192f64a1666733f17-2048x1152.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/46b102414d13eccddfdfac7192f64a1666733f17-2048x1152.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/46b102414d13eccddfdfac7192f64a1666733f17-2048x1152.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/46b102414d13eccddfdfac7192f64a1666733f17-2048x1152.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/46b102414d13eccddfdfac7192f64a1666733f17-2048x1152.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/46b102414d13eccddfdfac7192f64a1666733f17-2048x1152.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2048 2048w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/46b102414d13eccddfdfac7192f64a1666733f17-2048x1152.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;900&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;So we went for it!&lt;/h3&gt;&lt;p&gt;First, we &lt;strong&gt;developed a plan&lt;/strong&gt;. We met with Serenity to understand their vision. White-boarded &amp;amp; brainstormed features. Suggested a &lt;em&gt;buy-once-keep-forever&lt;/em&gt; model for in-app purchases. Found and hired a designer. Then created estimates and timelines to bring everything into view.&lt;/p&gt;&lt;p&gt;Next, we &lt;strong&gt;helped raise money&lt;/strong&gt;. Determined realistic deliverables for the Kickstarter campaign. And, promoted it using our own social media channels.&lt;/p&gt;&lt;p&gt;We then &lt;strong&gt;developed a structured process&lt;/strong&gt;. Set up weekly meetings, GitHub projects, and Slack channels for ad-hoc conversation. And led in-person training to help Serenity create and publish new content.&lt;/p&gt;&lt;p&gt;Next up—&lt;strong&gt;build the app&lt;/strong&gt;. We kept Serenity informed by showing and sharing progress regularly in design reviews. Set up continuous integration using BuddyBuild. Helped them perform QA testing on every build.&lt;/p&gt;&lt;p&gt;All of the above created a robust environment to &lt;strong&gt;test and revise&lt;/strong&gt; the app. Not only did Serenity perform QA, so did the Kickstarter backers. We involved them. Incorporated their feedback. They became Aloe Bud’s beta testers… and new buddies. Cool, huh?&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;840&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Aloe Bud came to life&lt;/h3&gt;&lt;p&gt;Serenity had some personal struggles in life. They were not alone. But their love and vision put an app into the world—one that was missing. One that offered:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;A kind, self-care, iOS app&lt;/strong&gt; to make people feel good, not guilty or shameful&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Gentle, freemium reminders&lt;/strong&gt; to build healthy habits&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Premium reminders&lt;/strong&gt; they could buy and/or create themselves&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Journaling&lt;/strong&gt; to help users capture their thoughts and feelings&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Illustrations and animations&lt;/strong&gt; to bring the app to life&lt;/li&gt;&lt;li&gt;An app &lt;strong&gt;within their Kickstarter budget&lt;/strong&gt; of $40K (though they raised $50K)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;We wrote Aloe Bud using the Swift programming language. Why Swift? Because… Swift is easy to write. Runs fast. Crashes less. And, is widely known in the iOS development community. This empowers Serenity to be in control of the code and not be bound to any single vendor.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;840&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;In Serenity’s own words…&lt;/h3&gt;&lt;blockquote&gt;“I was mentally sick while building Aloe Bud. Yet, I received tremendous support from the Internet and from the Lickability team. I highly recommend Lickability for all iOS projects. They built Aloe Bud with me—it was a great experience.”&lt;br&gt;&lt;strong&gt;Serenity Discko&lt;/strong&gt; ∙ Founder of Aloe Bud&lt;/blockquote&gt;&lt;h3&gt;By the numbers&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;2.2K app reviews&lt;/li&gt;&lt;li&gt;4.8 stars average rating&lt;/li&gt;&lt;li&gt;#8 in Health &amp;amp; Fitness&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://apps.apple.com/us/story/id1399901557&quot;&gt;App of the Day&lt;/a&gt; on the App Store&lt;/li&gt;&lt;li&gt;Over 1.2M unique users&lt;/li&gt;&lt;li&gt;9.1M reminders created&lt;/li&gt;&lt;li&gt;7.8M tasks completed&lt;/li&gt;&lt;li&gt;Lots of good press, too—including in &lt;a href=&quot;https://www.wired.com/story/amber-discko-aloe-bud/&quot;&gt;Wired Magazine&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9d7e2e83459b3458b30434f0a8ec21452cb3a437-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/9d7e2e83459b3458b30434f0a8ec21452cb3a437-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/9d7e2e83459b3458b30434f0a8ec21452cb3a437-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/9d7e2e83459b3458b30434f0a8ec21452cb3a437-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/9d7e2e83459b3458b30434f0a8ec21452cb3a437-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/9d7e2e83459b3458b30434f0a8ec21452cb3a437-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/9d7e2e83459b3458b30434f0a8ec21452cb3a437-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/9d7e2e83459b3458b30434f0a8ec21452cb3a437-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9d7e2e83459b3458b30434f0a8ec21452cb3a437-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;840&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Need some TLC for your app?&lt;/h3&gt;&lt;p&gt;Like we did with Aloe Bud? That’s what we’re here for. &lt;a href=&quot;https://lickability.com/contact&quot;&gt;&lt;strong&gt;Let’s talk&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Banks Suck</title><link>https://lickability.com/blog/2020-bank-switch/</link><guid isPermaLink="true">https://lickability.com/blog/2020-bank-switch/</guid><description>Here’s how we chose one for our small business anyway</description><pubDate>Wed, 02 Dec 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Banks suck. The financial industry is full of hidden and predatory fees, outdated software, and horrific customer support. From &lt;a href=&quot;https://en.wikipedia.org/wiki/Wells_Fargo_account_fraud_scandal&quot;&gt;defrauding customers with millions of fake accounts&lt;/a&gt; to &lt;a href=&quot;https://www.icij.org/investigations/fincen-files/&quot;&gt;facilitating crimes against humanity across the globe&lt;/a&gt;, the banking industry in particular is an irredeemable blight on this planet that will find every way to extract the most money possible from you and your business, often illegally or unethically. When it came time for our small business to switch banks, being clear-eyed about the state of this industry made for a daunting but ultimately fruitful search for a place to keep our hard-won earnings. Here‘s why and how we did it.&lt;/p&gt;&lt;h3&gt;Local Man Ruins Everything&lt;/h3&gt;&lt;p&gt;Many years ago, when we were young and naive, we decided to open a small business bank account with Citibank since they had a close local branch and solid online reviews. This decision turned into years of pain, frustration, and fees. As our business grew, so did our frustration—but so too did the cost of switching to another institution. Other banks had similar fees and services, comparable ethical entanglements, and software that was nearly as bad. However, the issues with Citibank kept mounting.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f6f5b4e67b61b5973a7a45974ab08cce6a174484-1078x517.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=270 270w, https://cdn.sanity.io/images/nkt6o869/production/f6f5b4e67b61b5973a7a45974ab08cce6a174484-1078x517.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=539 539w, https://cdn.sanity.io/images/nkt6o869/production/f6f5b4e67b61b5973a7a45974ab08cce6a174484-1078x517.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=809 809w, https://cdn.sanity.io/images/nkt6o869/production/f6f5b4e67b61b5973a7a45974ab08cce6a174484-1078x517.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1078 1078w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f6f5b4e67b61b5973a7a45974ab08cce6a174484-1078x517.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1078&quot; width=&quot;1078&quot; height=&quot;517&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Citibank‘s website is unreliable and at times unusable for basic operations; their data doesn’t play nice with our bookkeeping software, &lt;a href=&quot;https://www.xero.com/us/&quot;&gt;Xero&lt;/a&gt;; and their customer support often creates more problems than it solves. And on top of all that, just as small businesses desperately needed their help the most during a global pandemic and recession, they decided to &lt;a href=&quot;https://www.nytimes.com/2020/04/22/business/sba-loans-ppp-coronavirus.html&quot;&gt;streamline PPP loan applications only for their largest businesses&lt;/a&gt; so that they could collect the most fees before they opened a single application for any other business. After this, we knew there was no way we could remain at Citibank—and so it was time to switch, no matter how much effort it would take.&lt;/p&gt;&lt;h3&gt;Gathering Information&lt;/h3&gt;&lt;p&gt;There are over 4,500 banks in the United States, and likely tens of thousands of different accounts to choose from. In order to compare a variety of banks and account types but also to limit the scope of the search, we compiled a list of 42 bank accounts based on several sources: lists of the top business bank accounts, recommendations from our peers, recommendations from our accountant and bookkeepers, and every &lt;a href=&quot;https://www.xero.com/us/partner-programs/banking-partners/us-banks/&quot;&gt;Xero Banking Partner&lt;/a&gt; that would integrate well with our bookkeeping software. Once we had a list of accounts, we went to work gathering all the data we could possibly need for comparisons.&lt;/p&gt;&lt;p&gt;After the &lt;a href=&quot;https://en.wikipedia.org/wiki/Truth_in_Savings_Act&quot;&gt;Truth in Savings Act&lt;/a&gt; was passed and enforcement was transferred to the Consumer Financial Protection Bureau, banks now need to disclose every fee for an account in what‘s called a &lt;em&gt;schedule of fees&lt;/em&gt;. Using these publicly-available schedules of fees and information on the web, we devised shared categories we could use to compare each of our potential new banks, such as the type of account, monthly fees, interest, wire fees, and many more. Coming to a comprehensive total of 30 data points for each account, we needed a way to log, compare, and analyze this data, and as any self-respecting nerd knows: there‘s no better tool for this kind of structured information than a spreadsheet.&lt;/p&gt;&lt;h3&gt;Criteria&lt;/h3&gt;&lt;p&gt;To avoid bias toward any particular account, we decided to set the criteria for qualifying as a “finalist” before entering even a single piece of data into our spreadsheet, and used pivot tables to filter the relevant accounts in real time as they were entered. Here‘s the relatively lenient criteria that we decided was important to our business:&lt;/p&gt;&lt;h4&gt;🤝 Xero Integration&lt;/h4&gt;&lt;p&gt;Xero compatibility was a huge priority for us, as it could affect how much time is spent on bookkeeping and the quality of entries that we use to analyze all of our financial data.&lt;/p&gt;&lt;h4&gt;🔐 FDIC Insurance&lt;/h4&gt;&lt;p&gt;If anything happened to the financial institution, we wanted our funds to be insured by the federal government, up to federal limits. These accounts are safe bets that in almost all cases do not lose value.&lt;/p&gt;&lt;h4&gt;💸 PPP&lt;/h4&gt;&lt;p&gt;This was a really simple standard: if the bank prioritized large clients over smaller ones with PPP applications, that was not a bank we wanted to do business with. Any bank that would use funds meant to help small businesses to instead enrich themselves on large business fees is simply not a safe place to park your cash reserves as a small business.&lt;/p&gt;&lt;h4&gt;📱 iOS App&lt;/h4&gt;&lt;p&gt;This was the most minor of all the criteria, but since we‘re a mobile-focused company that specializes in making great iOS apps, we wanted to work with a company with at least a decent app. This can also sometimes be a proxy for software quality across the board, as lack of investment in mobile can be an indicator of sub-standard software elsewhere.&lt;/p&gt;&lt;div class=&quot;not-prose&quot;&gt; &lt;style&gt;astro-island,astro-slot,astro-static-slot{display:contents}&lt;/style&gt;&lt;script&gt;(()=&gt;{var l=(s,i,o)=&gt;{let r=async()=&gt;{await(await s())()},t=typeof i.value==&quot;object&quot;?i.value:void 0,c={rootMargin:t==null?void 0:t.rootMargin},n=new IntersectionObserver(e=&gt;{for(let a of e)if(a.isIntersecting){n.disconnect(),r();break}},c);for(let e of o.children)n.observe(e)};(self.Astro||(self.Astro={})).visible=l;window.dispatchEvent(new Event(&quot;astro:visible&quot;));})();;(()=&gt;{var A=Object.defineProperty;var g=(i,o,a)=&gt;o in i?A(i,o,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[o]=a;var d=(i,o,a)=&gt;g(i,typeof o!=&quot;symbol&quot;?o+&quot;&quot;:o,a);{let i={0:t=&gt;m(t),1:t=&gt;a(t),2:t=&gt;new RegExp(t),3:t=&gt;new Date(t),4:t=&gt;new Map(a(t)),5:t=&gt;new Set(a(t)),6:t=&gt;BigInt(t),7:t=&gt;new URL(t),8:t=&gt;new Uint8Array(t),9:t=&gt;new Uint16Array(t),10:t=&gt;new Uint32Array(t),11:t=&gt;1/0*t},o=t=&gt;{let[l,e]=t;return l in i?i[l](e):void 0},a=t=&gt;t.map(o),m=t=&gt;typeof t!=&quot;object&quot;||t===null?t:Object.fromEntries(Object.entries(t).map(([l,e])=&gt;[l,o(e)]));class y extends HTMLElement{constructor(){super(...arguments);d(this,&quot;Component&quot;);d(this,&quot;hydrator&quot;);d(this,&quot;hydrate&quot;,async()=&gt;{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest(&quot;astro-island[ssr]&quot;);if(e){e.addEventListener(&quot;astro:hydrate&quot;,this.hydrate,{once:!0});return}let c=this.querySelectorAll(&quot;astro-slot&quot;),n={},h=this.querySelectorAll(&quot;template[data-astro-template]&quot;);for(let r of h){let s=r.closest(this.tagName);s!=null&amp;&amp;s.isSameNode(this)&amp;&amp;(n[r.getAttribute(&quot;data-astro-template&quot;)||&quot;default&quot;]=r.innerHTML,r.remove())}for(let r of c){let s=r.closest(this.tagName);s!=null&amp;&amp;s.isSameNode(this)&amp;&amp;(n[r.getAttribute(&quot;name&quot;)||&quot;default&quot;]=r.innerHTML)}let p;try{p=this.hasAttribute(&quot;props&quot;)?m(JSON.parse(this.getAttribute(&quot;props&quot;))):{}}catch(r){let s=this.getAttribute(&quot;component-url&quot;)||&quot;&lt;unknown&gt;&quot;,v=this.getAttribute(&quot;component-export&quot;);throw v&amp;&amp;(s+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${s}`,this.getAttribute(&quot;props&quot;),r),r}let u;await this.hydrator(this)(this.Component,p,n,{client:this.getAttribute(&quot;client&quot;)}),this.removeAttribute(&quot;ssr&quot;),this.dispatchEvent(new CustomEvent(&quot;astro:hydrate&quot;))});d(this,&quot;unmount&quot;,()=&gt;{this.isConnected||this.dispatchEvent(new CustomEvent(&quot;astro:unmount&quot;))})}disconnectedCallback(){document.removeEventListener(&quot;astro:after-swap&quot;,this.unmount),document.addEventListener(&quot;astro:after-swap&quot;,this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute(&quot;await-children&quot;)||document.readyState===&quot;interactive&quot;||document.readyState===&quot;complete&quot;)this.childrenConnectedCallback();else{let e=()=&gt;{document.removeEventListener(&quot;DOMContentLoaded&quot;,e),c.disconnect(),this.childrenConnectedCallback()},c=new MutationObserver(()=&gt;{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&amp;&amp;this.lastChild.nodeValue===&quot;astro:end&quot;&amp;&amp;(this.lastChild.remove(),e())});c.observe(this,{childList:!0}),document.addEventListener(&quot;DOMContentLoaded&quot;,e)}}async childrenConnectedCallback(){let e=this.getAttribute(&quot;before-hydration-url&quot;);e&amp;&amp;await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute(&quot;opts&quot;)),c=this.getAttribute(&quot;client&quot;);if(Astro[c]===void 0){window.addEventListener(`astro:${c}`,()=&gt;this.start(),{once:!0});return}try{await Astro[c](async()=&gt;{let n=this.getAttribute(&quot;renderer-url&quot;),[h,{default:p}]=await Promise.all([import(this.getAttribute(&quot;component-url&quot;)),n?import(n):()=&gt;()=&gt;{}]),u=this.getAttribute(&quot;component-export&quot;)||&quot;default&quot;;if(!u.includes(&quot;.&quot;))this.Component=h[u];else{this.Component=h;for(let f of u.split(&quot;.&quot;))this.Component=this.Component[f]}return this.hydrator=p,this.hydrate},e,this)}catch(n){console.error(`[astro-island] Error hydrating ${this.getAttribute(&quot;component-url&quot;)}`,n)}}attributeChangedCallback(){this.hydrate()}}d(y,&quot;observedAttributes&quot;,[&quot;props&quot;]),customElements.get(&quot;astro-island&quot;)||customElements.define(&quot;astro-island&quot;,y)}})();&lt;/script&gt;&lt;astro-island uid=&quot;1zBVwu&quot; prefix=&quot;r1&quot; component-url=&quot;/opt/build/repo/embeds/ClientEmbed.tsx&quot; component-export=&quot;ClientEmbed&quot; renderer-url=&quot;@astrojs/react/client.js&quot; props=&quot;{&amp;quot;providerId&amp;quot;:[0,&amp;quot;twitter&amp;quot;],&amp;quot;wrapperClass&amp;quot;:[0,&amp;quot;aspect-video&amp;quot;],&amp;quot;tweetLink&amp;quot;:[0,&amp;quot;bcapps/status/1296542475448877056&amp;quot;]}&quot; ssr=&quot;&quot; client=&quot;visible&quot; before-hydration-url=&quot;astro:scripts/before-hydration.js&quot; opts=&quot;{&amp;quot;name&amp;quot;:&amp;quot;ClientEmbed&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;rootMargin&amp;quot;:&amp;quot;400px&amp;quot;}}&quot; await-children=&quot;&quot;&gt;&lt;div class=&quot;aspect-video&quot;&gt;&lt;div data-testid=&quot;general-observer&quot; class=&quot;mdx-embed&quot;&gt;&lt;div style=&quot;height:0;width:100%&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--astro:end--&gt;&lt;/astro-island&gt; &lt;/div&gt;&lt;p&gt;Once it was all said and done, we ended up with a massive spreadsheet with 1,260 individual entries. We then set up statistics for the entries showing the types of accounts (mostly checking), two-factor authentication (mostly SMS), insurance type (almost all FDIC), and more, with a few pie charts to illustrate the breakdown. Finally, we rounded up the finalists based on the criteria above, to some surprising results.&lt;/p&gt;&lt;h3&gt;Finalists&lt;/h3&gt;&lt;p&gt;Even with very simple criteria for entry, our list of bank account finalists ended up being quite exclusive: only six met the threshold to become finalists. Of those, we cut half for lack of features, nonexistent two-factor authentication, and lack of adoption. We signed up for the remaining accounts and went to work evaluating them. Here are the three that made the cut:&lt;/p&gt;&lt;h4&gt;🏦 Azlo&lt;/h4&gt;&lt;p&gt;&lt;a href=&quot;https://www.azlo.com&quot;&gt;Azlo&lt;/a&gt; was the bank we saw appear most on “best small business bank account” lists across the internet. They had almost no fees, mentioned a Xero integration, and had friendly branding.&lt;/p&gt;&lt;h4&gt;🏦 Brex&lt;/h4&gt;&lt;p&gt;&lt;a href=&quot;https://www.brex.com/product/cash-management-account/&quot;&gt;Brex&lt;/a&gt; is a popular credit card company for startups, but their relatively new Cash account was also interesting to us. It also had almost no fees, the interface seemed pleasant, and their account was a “cash management account,” where a portion could be in a checking account and a portion could be invested to potentially make a greater return. We also liked that they supported accounts with custom access for bookkeepers.&lt;/p&gt;&lt;h4&gt;🏦 Mercury&lt;/h4&gt;&lt;p&gt;&lt;a href=&quot;https://mercury.com&quot;&gt;Mercury&lt;/a&gt; is a startup-focused bank account that came highly recommended by our accountant. They had the lowest fees of all, a beautiful interface, and were a Xero Partner. They supported customized access accounts for bookkeepers and other types of users, and when signing up you receive both a checking and savings account.&lt;/p&gt;&lt;h3&gt;Winner&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/8837c245e35122358d285a2da7ffd018b12fbc1c-1101x225.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=275 275w, https://cdn.sanity.io/images/nkt6o869/production/8837c245e35122358d285a2da7ffd018b12fbc1c-1101x225.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=551 551w, https://cdn.sanity.io/images/nkt6o869/production/8837c245e35122358d285a2da7ffd018b12fbc1c-1101x225.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=826 826w, https://cdn.sanity.io/images/nkt6o869/production/8837c245e35122358d285a2da7ffd018b12fbc1c-1101x225.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1101 1101w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/8837c245e35122358d285a2da7ffd018b12fbc1c-1101x225.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1101&quot; width=&quot;1101&quot; height=&quot;225&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;After getting a chance to use all of the above for a few weeks, the choice became clear: &lt;strong&gt;the bank account for us was Mercury&lt;/strong&gt;. Azlo‘s website and app were clunky and difficult to use (they’ve since done a full redesign); Brex‘s Cash account features were a bit too limited for our use case; and Mercury was easy to use, sent much more frequent and higher quality data to Xero, had powerful debit card controls, and had the best iOS banking app I’ve ever used.&lt;/p&gt;&lt;p&gt;If you‘re doing a similar comparison, or just want to check our work and look at some of the statistics, we’re publicly releasing &lt;a href=&quot;https://docs.google.com/spreadsheets/d/1xWbTE97-3TPcBVIY9NXQkTnEc5tG_XgYcPxhDnpkqks&quot;&gt;the spreadsheet that we used to compare banks&lt;/a&gt;. The data is accurate as of mid-2020, so may already be out of date in some cases. If you think it would be useful to you, feel free to duplicate and use it for your own comparison! Just remember this fact that we had to learn the hard way: banks suck. But at least your small business bank account doesn‘t have to.&lt;/p&gt; </content:encoded><author>Brian Capps</author></item><item><title>Swift on Raspberry Pi Workshop: Part 3</title><link>https://lickability.com/blog/swift-on-raspberry-pi-workshop-part-3/</link><guid isPermaLink="true">https://lickability.com/blog/swift-on-raspberry-pi-workshop-part-3/</guid><description>Wrapping up our project</description><pubDate>Fri, 20 Nov 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is the third and final post in our three-part series about setting up a Raspberry Pi to build a distance measurement system for an autonomous car. In &lt;a href=&quot;https://lickability.com/blog/swift-on-raspberry-pi-workshop/&quot;&gt;Part 1&lt;/a&gt; of this series, we set up a basic circuit to control LEDs via the Raspberry Pi; and in &lt;a href=&quot;https://lickability.com/blog/swift-on-raspberry-pi-workshop-part-2/&quot;&gt;Part 2&lt;/a&gt;, we started controlling those LEDs using Swift. If you‘re coming to this post first, I recommend checking out those posts first.&lt;/p&gt;&lt;p&gt;In this post, we‘re going to get started by &lt;strong&gt;adding a speaker to our circuit&lt;/strong&gt; to make a sound.&lt;/p&gt;&lt;h3&gt;Adding a speaker to our circuit&lt;/h3&gt;&lt;p&gt;If you examine the picture below, you‘ll see an example circuit with the speaker connected. This circuit also has a few extra LEDs that we’ll use in our final circuit. For now, let‘s focus on connecting the speaker. For this, I’ll use ports 9A and 10A on the breadboard. You can use whichever ports you like. I‘m also going to use GPIO 17 (pin #11) on my Raspberry Pi to supply the voltage. For the ground connection, I’ll tap into my ground rail on the breadboard.&lt;/p&gt;&lt;p&gt;In Part 1, we used pin #9 from our Raspberry Pi to supply a ground connection to our breadboard. In order to use this ground, I‘ll use a jumper wire to go from the ground rail to port 9A. Then, I’ll connect a wire from GPIO 17 (pin #11) of my Raspberry Pi to port 10A of my breadboard. These connections are all we need to supply power to any port on row 10 A–E, as well as ground to any port on row 9 A–E. The final step is to place your speaker on any port in rows 9 and 10 A–E.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/31a635448a8a2b9482c9fb03e7de2854a1d2b1c6-1755x1161.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/31a635448a8a2b9482c9fb03e7de2854a1d2b1c6-1755x1161.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/31a635448a8a2b9482c9fb03e7de2854a1d2b1c6-1755x1161.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/31a635448a8a2b9482c9fb03e7de2854a1d2b1c6-1755x1161.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/31a635448a8a2b9482c9fb03e7de2854a1d2b1c6-1755x1161.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1755 1755w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/31a635448a8a2b9482c9fb03e7de2854a1d2b1c6-1755x1161.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1058&quot;/&gt;  &lt;/figure&gt; &lt;h4&gt;Controlling the speaker with Swift&lt;/h4&gt;&lt;p&gt;Luckily for us, our speaker is a simple polarized component just like our LED. This means that it has a positive and negative side. In order to emit a sound, we just need to supply voltage to the positive side while grounding the negative side. The speaker will then make a sound as long as voltage is supplied. To make the sound stop, we just cut off the voltage or set it to low (0).&lt;/p&gt;&lt;p&gt;If you recall how we controlled our LED in the previous section, we will control our speaker in a similar fashion. Let‘s evaluate the code example below.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;speakerLed.swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; SwiftyGPIO&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// SwiftyGPIO configuration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; gpio = SwiftyGPIO.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GPIOs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;RaspberryPi3&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;guard&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; led = gpio[.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;P2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    fatalError&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Could not initialize led.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;guard&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; speaker = gpio[.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;P17&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    fatalError&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Could not initialize speaker.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;//4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;led.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;direction&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;OUT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;//5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;speaker.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;direction&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;OUT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;//6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;led.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;//7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;speaker.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;ol&gt;&lt;li&gt;Sets up our GPIO pins to be used.&lt;/li&gt;&lt;li&gt;A reference to the LED controlled by GPIO 2.&lt;/li&gt;&lt;li&gt;A reference to the speaker controlled by GPIO 17.&lt;/li&gt;&lt;li&gt;Here we set the direction of the port. In this case, we are saying that the direction of GPIO 2 should be .OUT as in output. This means we want to output a signal/voltage from this pin.&lt;/li&gt;&lt;li&gt;Sets the direction of GPIO 17 to be an .OUT as well.&lt;/li&gt;&lt;li&gt;This sets the value of GPIO 2, which references our LED, to 1 or High. In layman‘s terms, this turns on the LED.&lt;/li&gt;&lt;li&gt;This sets the value of GPIO 17, which references our speaker, to 1 or High. This emits a sound from the speaker.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;If you build and execute this code on your Raspberry Pi, you should hear a sound coming from your speaker, and your LED should be lit.&lt;/p&gt;&lt;h4&gt;Introducing the HC-SR04 Ultrasonic Sensor&lt;/h4&gt;&lt;p&gt;Now that we know how to control a speaker and LED, we can leverage that knowledge to tie them into the final component of our system. This is by far the most complicated part—up to this point, we have been working with simple 2-pronged polarized components, but the HC-SRO4 has 4 prongs:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;VCC&lt;/strong&gt; – This is our voltage pin. Here we supply the sensor with 5 volts. This is slightly more voltage than our LED and speaker require.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Trig&lt;/strong&gt; – This is our trigger pin. We use this pin to broadcast a sound wave.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Echo&lt;/strong&gt; – This is our echo pin. We use this pin to listen for the reply of the sound wave we broadcasted earlier.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Gnd&lt;/strong&gt; – This is our ground pin.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;To measure distance, we are going to emit a sound, wait for a response, and measure the time it takes to get that response. This will allow us to know how far an object is from us. Writing the logic for this in Swift can be a bit tricky, so we are going to leverage the help of a package called &lt;a href=&quot;https://github.com/konifer44/HCSR04.swift&quot;&gt;HCSR04.swift&lt;/a&gt;.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;As of 11/16/20, this package hasn‘t been maintained in a few years and does not work with the latest package manager. Being that it is a single file, and shares a dependency we already have in our project, we are just going to manually copy the &lt;strong&gt;HCSR04.swift&lt;/strong&gt; file into our project. This class will provide our measurement calculation for us.&lt;/p&gt;  &lt;/aside&gt;&lt;p&gt;If you have been following along with me, your project navigator should look like the image below:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e057eed032ff2e422135e7f2218f366b14339cf9-536x526.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=268 268w, https://cdn.sanity.io/images/nkt6o869/production/e057eed032ff2e422135e7f2218f366b14339cf9-536x526.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=536 536w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e057eed032ff2e422135e7f2218f366b14339cf9-536x526.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=536&quot; width=&quot;536&quot; height=&quot;526&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;With that code in place, we can now add some logic to our &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;main.swift&lt;/code&gt; file and incorporate our HC-SR04 sensor. I‘ve also added a few extra LEDs to my circuit so that we can have our full monitoring system. See the code below:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;speakerLedHCSR04.swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; SwiftyGPIO&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Foundation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// SwiftyGPIO configuration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; gpio = SwiftyGPIO.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GPIOs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;RaspberryPi3&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;guard&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; redLED = gpio[.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;P2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    fatalError&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Could not initialize red led.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;guard&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; yellowLED = gpio[.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;P3&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    fatalError&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Could not initialize yellow led.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;guard&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; greenLED = gpio[.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;P4&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    fatalError&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Could not initialize green led.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;guard&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; speaker = gpio[.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;P17&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    fatalError&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Could not initialize speaker.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;redLED.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;direction&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;OUT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;yellowLED.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;direction&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;OUT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;greenLED.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;direction&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;OUT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;speaker.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;direction&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;OUT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;//4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;redLED.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;yellowLED.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;greenLED.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;speaker.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;//5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; sensor = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;HCSR04&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;usedRaspberry&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;RaspberryPi3&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;echoConnectedPin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;P22&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;triggerConnectedPin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;P27&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;maximumSensorRange&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;400&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;//6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;while&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    do&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; distance = &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; sensor.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;measureDistance&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;The distance is: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;distance&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        switch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; distance {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;..&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;            print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Red zone&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            redLED.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            speaker.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            yellowLED.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            greenLED.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;..&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;            print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Yellow zone&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            redLED.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            speaker.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            yellowLED.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            greenLED.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        default&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;            print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Green zone&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            redLED.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            speaker.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            yellowLED.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            greenLED.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        Thread.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forTimeInterval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.6&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;There was an error: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;error.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;localizedDescription&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Let‘s go through what the code is doing:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;In this step, we are initializing our GPIO pins.&lt;/li&gt;&lt;li&gt;Here we are creating references to the various pins that correspond to our LEDs. These pin assignments directly reflect the cables that connect back to my Raspberry Pi as well as the connections on the breadboard.&lt;/li&gt;&lt;li&gt;Here we set the direction of our ports. In this case, all of our LEDs are functioning as outputs because they are transforming voltage being supplied to them versus providing some sort of input voltage back to our Raspberry Pi.&lt;/li&gt;&lt;li&gt;In this code block we ensure that every component starts in the off position. This ensures that our LEDs and speaker all start in the same state regardless of the state they were left in when the program last exited.&lt;/li&gt;&lt;li&gt;This line sets up our HC-SRO4 sensor. This is possible because of the code that we copied in from the library. The pins specified are specifically based on the ports that my sensor is plugged into.&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Note:&lt;/strong&gt; The maximum sensor range value is an arbitrary value that I chose based on the ranging distance listed on the box of the sensor. The maximum range is 500cm, but I decided to use 400cm to limit any chances of error.&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;This is where all of the distance measurement happens. We first create an infinite while loop so that our sensor is continuously monitoring as long as the program is running. Our sensor has a method called &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;measure​Distance()&lt;/code&gt; that returns a &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Double&lt;/code&gt; in centimeters of the estimated distance. We then use a &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;switch&lt;/code&gt; statement over that distance returned to determine what we want to do:&lt;ol&gt;&lt;li&gt;If the distance range is between 0cm and 4.9cm, we consider this our “Red zone”. If we are in this zone, we want to turn on the red LED as well as the speaker.&lt;/li&gt;&lt;li&gt;If the distance range is between 5cm and 9.9cm, we consider this our “Yellow zone” and turn on the yellow LED.&lt;/li&gt;&lt;li&gt;If the distance range is anything else, we consider this our green zone and turn on the green LED.&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;In order to ensure that the sensor has time to process the measurements, we sleep the thread or pause for 0.6 seconds in between each measurement. With all of that in place, you now have a fully function distance measurement system written in Swift!&lt;/p&gt;&lt;h3&gt;Summary&lt;/h3&gt;&lt;p&gt;In this series, we learned how to work with a breadboard, resistors, LEDs, HCSR04 sensor, buzzer and a Raspberry Pi to create a system that can measure differences. Our first step was to build a basic circuit that we could control with no Swift involvement, allowing us to verify that our connections are good and that we‘re not making a mistake in Xcode or in the transfer of our code. In Part 2, we introduced &lt;a href=&quot;https://github.com/digimarktech/Xport&quot;&gt;Xport&lt;/a&gt; and started writing Swift code to actually drive the logic on our board. Finally, we added a &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;HCSR04&lt;/code&gt; sensor to the equation so that we could measure distances and interact with the speaker and LEDs. Remember, most components require a (0) or a (1) to turn them off or on. With that knowledge, you can write some interesting logic that can interact with hardware based on the state of certain sensors.&lt;/p&gt;&lt;p&gt;I had a tremendous amount of fun hosting this workshop, and met so many awesome developers from all over the world. However, the proudest moment for me was being able to have my firstborn son present to see Daddy in his element. The Raspberry Pi is an amazing device for all ages, and after the workshop, my son was anxious for us to build a project together. If your target group is children, you can also use programs like &lt;a href=&quot;https://scratch.mit.edu&quot;&gt;Scratch&lt;/a&gt; to control your circuits. I highly recommend visiting the &lt;a href=&quot;https://projects.raspberrypi.org/en/&quot;&gt;projects&lt;/a&gt; page on the official Raspberry Pi website for ideas.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e51714ddf515463ecdabd0f6f6da1702291f557b-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/e51714ddf515463ecdabd0f6f6da1702291f557b-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/e51714ddf515463ecdabd0f6f6da1702291f557b-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/e51714ddf515463ecdabd0f6f6da1702291f557b-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/e51714ddf515463ecdabd0f6f6da1702291f557b-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/e51714ddf515463ecdabd0f6f6da1702291f557b-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/e51714ddf515463ecdabd0f6f6da1702291f557b-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/e51714ddf515463ecdabd0f6f6da1702291f557b-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3024 3024w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e51714ddf515463ecdabd0f6f6da1702291f557b-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;2133&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a67d2a29cb210f9692e5d7518eb49ad27f4ac498-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/a67d2a29cb210f9692e5d7518eb49ad27f4ac498-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/a67d2a29cb210f9692e5d7518eb49ad27f4ac498-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/a67d2a29cb210f9692e5d7518eb49ad27f4ac498-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/a67d2a29cb210f9692e5d7518eb49ad27f4ac498-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/a67d2a29cb210f9692e5d7518eb49ad27f4ac498-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/a67d2a29cb210f9692e5d7518eb49ad27f4ac498-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/a67d2a29cb210f9692e5d7518eb49ad27f4ac498-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3024 3024w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a67d2a29cb210f9692e5d7518eb49ad27f4ac498-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;2133&quot;/&gt;  &lt;/figure&gt; &lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;&lt;a href=&quot;https://github.com/digimarktech/trySwift2019&quot;&gt;Here&lt;/a&gt; is a Github repo I used for the workshop with some additional resources.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Marc Aupont</author></item><item><title>Swift on Raspberry Pi Workshop: Part 2</title><link>https://lickability.com/blog/swift-on-raspberry-pi-workshop-part-2/</link><guid isPermaLink="true">https://lickability.com/blog/swift-on-raspberry-pi-workshop-part-2/</guid><description>Building Swift on a Raspberry Pi</description><pubDate>Wed, 18 Nov 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is the second post in a three-part series about setting up a Raspberry Pi to build a distance measurement system for an autonomous car. In &lt;a href=&quot;https://lickability.com/blog/swift-on-raspberry-pi-workshop/&quot;&gt;Part 1&lt;/a&gt;, we set up a basic circuit to control LEDs via the Raspberry Pi. In this post, we‘ll cover how to &lt;strong&gt;create the external build system we need to build and transfer code from a computer to our Raspberry Pi&lt;/strong&gt; and start writing some Swift.&lt;/p&gt;&lt;h3&gt;Using Swift to control the LEDs&lt;/h3&gt;&lt;p&gt;We’ve built our circuit and verified that everything is hooked up with the WiringPi library. This is nice, but wouldn’t it be even nicer to use Swift to control our circuit? To do that, we’ll need to first create an external build system, and then create a Swift executable package.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;We could write our Swift code in a text editor on the Raspberry Pi, but that would be cumbersome. I don‘t know about you, but since we’re writing Swift, I think it makes sense to develop in Xcode and use an external build system to compile the code on our Raspberry Pi.&lt;/p&gt;  &lt;/aside&gt;&lt;h4&gt;Creating an external build system&lt;/h4&gt;&lt;p&gt;The external build system will help us transport and build our Swift package on the Raspberry Pi. In the past, I used &lt;a href=&quot;https://github.com/thomaspaulmann/Swish&quot;&gt;Swish&lt;/a&gt; to sync my code to the Pi. Being that it is no longer maintained, it doesn‘t work quite right with the new structure of Swift packages. To help with this, I created a similar script using SPM called &lt;a href=&quot;https://github.com/digimarktech/Xport&quot;&gt;Xport&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;We‘re going to use &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Xport&lt;/code&gt; to perform a &lt;a href=&quot;https://www.raspberrypi.org/documentation/remote-access/ssh/rsync.md&quot;&gt;rsync&lt;/a&gt; between our computer and the Pi—&lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Xport&lt;/code&gt; uses &lt;a href=&quot;https://www.raspberrypi.org/documentation/remote-access/ssh/&quot;&gt;SSH&lt;/a&gt; to establish a connection with the Pi, which requires us to provide a username and password every time we connect. Because we‘re using Xcode to execute this script, we’ll need to set up passwordless SSH access to create a trusted connection between our computer and the Pi. Here‘s what we need to do:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a1e6101a4b0399edaa02595e5b645015ca20e296-1458x1056.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=365 365w, https://cdn.sanity.io/images/nkt6o869/production/a1e6101a4b0399edaa02595e5b645015ca20e296-1458x1056.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=729 729w, https://cdn.sanity.io/images/nkt6o869/production/a1e6101a4b0399edaa02595e5b645015ca20e296-1458x1056.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1094 1094w, https://cdn.sanity.io/images/nkt6o869/production/a1e6101a4b0399edaa02595e5b645015ca20e296-1458x1056.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1458 1458w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a1e6101a4b0399edaa02595e5b645015ca20e296-1458x1056.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1458&quot; width=&quot;1458&quot; height=&quot;1056&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Step 2&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;ol&gt;&lt;li&gt;Launch Xcode and create a new project. I‘m going to call mine &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Remote​Pi​Build&lt;/code&gt;. Be sure not to use any spaces.&lt;/li&gt;&lt;li&gt;For the project template, select &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Cross-platform&lt;/code&gt; and &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;External Build System&lt;/code&gt;. &lt;/li&gt;&lt;li&gt;Follow &lt;a href=&quot;https://www.raspberrypi.org/documentation/remote-access/ssh/passwordless.md&quot;&gt;these&lt;/a&gt; instructions to set up passwordless SSH access. To ensure that it‘s set up properly, try to SSH into your Pi—if you’re still prompted to enter a password, you may need to store your RSA key to your keychain with this command: &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;ssh-add -K ~/.ssh/id_rsa&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Once you‘re able to connect without entering a password, we can move on to creating our swift package and writing our Swift code!&lt;/li&gt;&lt;/ol&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/ed06d208b83342a1dd545f873eb41d03d0b4e178-1136x730.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=284 284w, https://cdn.sanity.io/images/nkt6o869/production/ed06d208b83342a1dd545f873eb41d03d0b4e178-1136x730.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=568 568w, https://cdn.sanity.io/images/nkt6o869/production/ed06d208b83342a1dd545f873eb41d03d0b4e178-1136x730.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=852 852w, https://cdn.sanity.io/images/nkt6o869/production/ed06d208b83342a1dd545f873eb41d03d0b4e178-1136x730.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1136 1136w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/ed06d208b83342a1dd545f873eb41d03d0b4e178-1136x730.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1136&quot; width=&quot;1136&quot; height=&quot;730&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Step 3&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h4&gt;Creating the Swift executable package&lt;/h4&gt;&lt;p&gt;We‘ve built the external build system that will sync and build our package to our Raspberry Pi, but we don’t have a package yet. That package is the Swift code that will be executed on the Pi to control our circuit.&lt;/p&gt;&lt;p&gt;To get started, we are going to make use of the &lt;a href=&quot;https://swift.org/package-manager/&quot;&gt;Swift Package Manager&lt;/a&gt; to create our package. With Xcode 12, you can create one from Xcode and manage your dependencies from there. From within your project, go to the Xcode menu and select &lt;strong&gt;File&lt;/strong&gt;, &lt;strong&gt;New&lt;/strong&gt;, &lt;strong&gt;Swift Package&lt;/strong&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d5ff2cbae7110df5c91abd6ac1cc13d3716fde27-1580x1192.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=395 395w, https://cdn.sanity.io/images/nkt6o869/production/d5ff2cbae7110df5c91abd6ac1cc13d3716fde27-1580x1192.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=790 790w, https://cdn.sanity.io/images/nkt6o869/production/d5ff2cbae7110df5c91abd6ac1cc13d3716fde27-1580x1192.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1185 1185w, https://cdn.sanity.io/images/nkt6o869/production/d5ff2cbae7110df5c91abd6ac1cc13d3716fde27-1580x1192.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1580 1580w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d5ff2cbae7110df5c91abd6ac1cc13d3716fde27-1580x1192.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1580&quot; width=&quot;1580&quot; height=&quot;1192&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Once you have reached the screen below, give your package a name—I‘m going to call mine &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;LED​Control&lt;/code&gt;. At the bottom of the screen, be sure to select your external build system project under the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Add to&lt;/code&gt; section to ensure that your package is part of the build system project.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c6ccdc27002a5d00d7fa9555e2eb2afadb242900-1764x1020.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/c6ccdc27002a5d00d7fa9555e2eb2afadb242900-1764x1020.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/c6ccdc27002a5d00d7fa9555e2eb2afadb242900-1764x1020.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/c6ccdc27002a5d00d7fa9555e2eb2afadb242900-1764x1020.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/c6ccdc27002a5d00d7fa9555e2eb2afadb242900-1764x1020.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1764 1764w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c6ccdc27002a5d00d7fa9555e2eb2afadb242900-1764x1020.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;925&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/306f1b95e7581386deca452b83fa2c6176491302-2012x1178.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/306f1b95e7581386deca452b83fa2c6176491302-2012x1178.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/306f1b95e7581386deca452b83fa2c6176491302-2012x1178.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/306f1b95e7581386deca452b83fa2c6176491302-2012x1178.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/306f1b95e7581386deca452b83fa2c6176491302-2012x1178.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/306f1b95e7581386deca452b83fa2c6176491302-2012x1178.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2012 2012w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/306f1b95e7581386deca452b83fa2c6176491302-2012x1178.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;937&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;At this point, our project is created and we need to install our dependencies. We are going to use &lt;a href=&quot;https://github.com/uraimo/SwiftyGPIO&quot;&gt;SwiftyGPIO&lt;/a&gt; to write the Swift code that controls our GPIO pins. Go to your &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Package.swift&lt;/code&gt; file, and add &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Swifty​GPIO&lt;/code&gt;. The result should look like this: &lt;/p&gt;&lt;p&gt;Now, let‘s add the code that is going to turn on our LED. In the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Sources\LED​Control&lt;/code&gt; directory create a file called &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;main.swift&lt;/code&gt;. Add the code in the photo below to the &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;main.swift&lt;/code&gt; file:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;First, retrieve the list of GPIOs available on the board. In this particular case, I am using the Raspberry Pi 3, but multiple board types are supported.&lt;/li&gt;&lt;li&gt;Once we have the list of GPIOs, we can get a reference to the ones we want to modify. In our case, we are interested in GPIO 2.&lt;/li&gt;&lt;li&gt;The next step is to set the direction of our pin(s). In this scenario, we set the GPIO 2 pin as an output pin so we can emit a 3.3V signal from it. This is how we will power our LED.&lt;/li&gt;&lt;li&gt;The final step is where we set the pin to high or (1). This turns on the LED.&lt;/li&gt;&lt;/ol&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/06a4650c3a0d4bec3b265ef0793faa60b56948a5-1824x908.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/06a4650c3a0d4bec3b265ef0793faa60b56948a5-1824x908.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/06a4650c3a0d4bec3b265ef0793faa60b56948a5-1824x908.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/06a4650c3a0d4bec3b265ef0793faa60b56948a5-1824x908.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/06a4650c3a0d4bec3b265ef0793faa60b56948a5-1824x908.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1824 1824w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/06a4650c3a0d4bec3b265ef0793faa60b56948a5-1824x908.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;796&quot;/&gt;  &lt;/figure&gt; &lt;h4&gt;Building the code&lt;/h4&gt;&lt;p&gt;Now, we are ready to build and compile our code on the Raspberry Pi. This is where &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Xport&lt;/code&gt; is going to come in. Before we do this, lets complete the configuration of our build tool so that it can communicate with our Pi. Select the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Remote​Pi​Build&lt;/code&gt; workspace and then the &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Remote​Pi​Build&lt;/code&gt; target. This will bring up the &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;External Build Tool Configuration&lt;/code&gt; with a few fields to fill out. Here is how we will fill them out.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Build Tool&lt;/strong&gt; – &lt;code index=&quot;2&quot; isInline=&quot;true&quot;&gt;/usr/local/bin/xport&lt;/code&gt;. This is the path where &lt;code index=&quot;4&quot; isInline=&quot;true&quot;&gt;Xport&lt;/code&gt; is installed.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Arguments&lt;/strong&gt; – &lt;code index=&quot;2&quot; isInline=&quot;true&quot;&gt;&amp;lt;username&amp;gt; &amp;lt;hostname&amp;gt;&lt;/code&gt;. The &lt;code index=&quot;4&quot; isInline=&quot;true&quot;&gt;username&lt;/code&gt; value is the username from your Raspberry Pi. The default is &lt;strong&gt;pi&lt;/strong&gt;. The &lt;code index=&quot;8&quot; isInline=&quot;true&quot;&gt;hostname&lt;/code&gt; value is the IP address of your Pi.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Directory&lt;/strong&gt; – This is the path for your Swift package.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/1a5ab7b5b0fd0925e31f62aae1f79e33c5444b80-865x380.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=216 216w, https://cdn.sanity.io/images/nkt6o869/production/1a5ab7b5b0fd0925e31f62aae1f79e33c5444b80-865x380.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=433 433w, https://cdn.sanity.io/images/nkt6o869/production/1a5ab7b5b0fd0925e31f62aae1f79e33c5444b80-865x380.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=649 649w, https://cdn.sanity.io/images/nkt6o869/production/1a5ab7b5b0fd0925e31f62aae1f79e33c5444b80-865x380.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=865 865w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/1a5ab7b5b0fd0925e31f62aae1f79e33c5444b80-865x380.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=865&quot; width=&quot;865&quot; height=&quot;380&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Once you have correctly filled those fields with your information, press &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;CMD + B&lt;/code&gt; to build the project and kick off the script responsible for transferring and building our newly created package on the Raspberry Pi. This script will copy over our package to the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Xport&lt;/code&gt; folder on the Raspberry Pi. At the moment, it doesn‘t actually execute or run the script, so we will need to manually do that from our terminal window.&lt;/p&gt;&lt;p&gt;Go ahead and remote into your Raspberry Pi via the terminal with the command &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;ssh &amp;lt;username&amp;gt;@&amp;lt;hostname&amp;gt;&lt;/code&gt;. From here, we‘ll navigate to the directory our project was transferred to. By default, &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Xport&lt;/code&gt; puts the completed project in &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;~Xport/&amp;lt;Project​Name&amp;gt;/&lt;/code&gt; where &lt;strong&gt;ProjectName&lt;/strong&gt; is the name of your external build system project. In my case, this is &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;~Xport/Remote​Pi​Build/&lt;/code&gt;. If I were to list all files in this directory via the &lt;code index=&quot;11&quot; isInline=&quot;true&quot;&gt;ls&lt;/code&gt; command, I can see my actual Swift package, &lt;code index=&quot;13&quot; isInline=&quot;true&quot;&gt;LED​Control&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Change into that directory with the command &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;cd LED​Control&lt;/code&gt;. From this directory, we can type the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;swift run --skip-build&lt;/code&gt; command, which will build our project, download any dependencies, and execute our code. If all works well, your LED should be on!&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;We added the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;--skip-build&lt;/code&gt; option because &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Xport&lt;/code&gt; already took care of building our package for us on the Raspberry Pi. This should speed up the time you are waiting for your code to run.&lt;/p&gt;  &lt;/aside&gt;&lt;p&gt;If you want to turn the LED off, go back to Xcode, change the LED value to &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;0&lt;/code&gt;, and build the project. The build process will transfer our updated files to the Pi where we can repeat the steps above to execute the new code. From this exercise, you can see how to manipulate multiple components just by updating their values from &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;0&lt;/code&gt; to &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;1&lt;/code&gt;. We will see more of this in the next post, when we work with our speaker and distance sensor.&lt;/p&gt;&lt;h3&gt;What‘s next&lt;/h3&gt;&lt;p&gt;So far, we‘ve learned how to configure our Raspberry Pi and build a small circuit on a breadboard. In &lt;a href=&quot;https://lickability.com/blog/swift-on-raspberry-pi-workshop-part-3/&quot;&gt;Part 3&lt;/a&gt; of this series, we‘ll integrate a speaker component, as well as the HC-SR04 sensor to measure distances.&lt;/p&gt; </content:encoded><author>Marc Aupont</author></item><item><title>Swift on Raspberry Pi Workshop: Part 1</title><link>https://lickability.com/blog/swift-on-raspberry-pi-workshop/</link><guid isPermaLink="true">https://lickability.com/blog/swift-on-raspberry-pi-workshop/</guid><description>Setting up a basic circuit</description><pubDate>Tue, 10 Nov 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The Raspberry Pi is a fun little device that everyone can learn from. From robotics to home automation, you can use it to do just about anything. In September 2019, I hosted a workshop at &lt;a href=&quot;https://www.tryswift.co/events/2019/nyc/&quot;&gt;try! Swift NYC&lt;/a&gt; where we used a Raspberry Pi to build a distance measurement system for an autonomous car.&lt;/p&gt;&lt;p&gt;How does this distance measurement system work? Essentially, we will measure the distance between an object and an HC-SR04 sensor. If we are far enough away from the sensor, a green LED will light up; if we get closer, a yellow LED will light up; and if we get too close, a red LED will light up and a speaker will emit a warning sound.&lt;/p&gt;&lt;p&gt;This is the first post in a three-part series about that workshop—today, we’ll cover &lt;strong&gt;how to set up a basic circuit that controls LEDs via the Raspberry Pi.&lt;/strong&gt; Let’s start building our system!&lt;/p&gt;&lt;h3&gt;What you‘ll need&lt;/h3&gt;&lt;p&gt;To put the full system together, you‘ll need the following parts:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://www.amazon.com/EL-CP-003-Breadboard-Solderless-Distribution-Connecting/dp/B01EV6LJ7G/ref=sr_1_2_sspa?crid=2JOUQVIRW9T5&amp;keywords=breadboard&amp;qid=1579645577&amp;sprefix=breadboard%2Caps%2C133&amp;sr=8-2-spons&amp;psc=1&amp;spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUFRSFJLT1lVQVI2SlomZW5jcnlwdGVkSWQ9QTA3NzIzODcyMTBNMDJSUFNEVlBNJmVuY3J5cHRlZEFkSWQ9QTA1MjQ4OTExNUtBUjVWT1BUQTk4JndpZGdldE5hbWU9c3BfYXRmJmFjdGlvbj1jbGlja1JlZGlyZWN0JmRvTm90TG9nQ2xpY2s9dHJ1ZQ==&quot;&gt;&lt;strong&gt;Breadboard&lt;/strong&gt;&lt;/a&gt; – This will act as the circuit board for our system, connecting all of our components so they can interact with one another.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Raspberry Pi&lt;/strong&gt; – This will be the brain of our system. I used a &lt;a href=&quot;https://www.raspberrypi.org/products/raspberry-pi-3-model-b-plus/&quot;&gt;Raspberry Pi 3 Model B+&lt;/a&gt;, but you can certainly use a &lt;a href=&quot;https://www.raspberrypi.org/products/raspberry-pi-zero/&quot;&gt;Pi Zero&lt;/a&gt; or any other model.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;LED Kit&lt;/strong&gt; – If you are hosting a workshop, you‘ll need multiple LEDs for each person to use. &lt;a href=&quot;https://www.amazon.com/5mm-Diffused-LED-Diode-Assortment/dp/B078RK19J2/ref=sr_1_2?crid=SBAEJSMN0BNW&amp;keywords=plusivo+led+kit&amp;qid=1579290258&amp;sprefix=plusivo+led%2Caps%2C127&amp;sr=8-2&quot;&gt;This kit&lt;/a&gt; is what I used in the workshop and it includes 100, 200-ohm resistors to protect the LEDs.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.amazon.com/BNYZWOT-Electronic-Magnetic-Continuous-Arduino/dp/B07VP6BTMV/ref=sr_1_9?keywords=piezo+active+buzzer&amp;qid=1579290573&amp;sr=8-9&quot;&gt;&lt;strong&gt;Active Buzzer&lt;/strong&gt;&lt;/a&gt; – This will be the speaker that alerts us when we get too close to the sensor.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.amazon.com/ELEGOO-HC-SR04-Ultrasonic-Distance-MEGA2560/dp/B01COSN7O6/ref=sr_1_4?crid=1Q7N8TQ70NMIF&amp;keywords=hcsr04+ultrasonic+sensor&amp;qid=1580092977&amp;sprefix=hcsr04%2Caps%2C133&amp;sr=8-4&quot;&gt;&lt;strong&gt;HC-SR04 Ultrasonic Sensor&lt;/strong&gt;&lt;/a&gt; – This sensor uses sonic waves to measure the distance of an object.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Resistors&lt;/strong&gt; – For this experiment, we need both 200-ohm resistors for our LEDs and &lt;a href=&quot;https://www.amazon.com/Projects-100EP5121K00-Ohm-Resistors-Pack/dp/B0185FIJ9A/ref=sr_1_4?crid=1EBWVSWQKD5J2&amp;keywords=1k+ohm+resistor&amp;qid=1580223173&amp;sprefix=1k+ohm+r%2Caps%2C132&amp;sr=8-4&quot;&gt;1K ohm resistors&lt;/a&gt; for our ultrasonic sensor circuit.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.amazon.com/JANSANE-PL2303TA-Serial-Console-Raspberry/dp/B07D9R5JFK/ref=sr_1_3?keywords=PL2303TA+USB+to+TTL+Serial+Cable&amp;qid=1580093077&amp;sr=8-3&quot;&gt;&lt;strong&gt;PL2303TA USB to TTL Serial Cable Debug Console Cable&lt;/strong&gt;&lt;/a&gt; – This cable is optional, but useful for workshop-like environments where you don‘t have access to a monitor to connect the Raspberry Pi to. It allows you to establish a serial connection between your computer and the Raspberry Pi, which will allow you to communicate with the Pi without having a network connection or monitor.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;For software, you‘ll need the following:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://apps.apple.com/us/app/xcode/id497799835?mt=12&quot;&gt;&lt;strong&gt;Xcode&lt;/strong&gt;&lt;/a&gt; – I am currently using Xcode 12. We will use Xcode to write our Swift code and send it to the Raspberry Pi.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/thomaspaulmann/Swish&quot;&gt;&lt;strong&gt;Swish&lt;/strong&gt;&lt;/a&gt; – This is a simple script for remote building your Swift projects on a Linux machine, such as a Raspberry Pi. If we didn‘t use this script, we would need to develop completely on the Raspberry Pi or manually copy all of our files from our MacBook to the Raspberry Pi.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://fritzing.org/home/&quot;&gt;&lt;strong&gt;Fritzing&lt;/strong&gt;&lt;/a&gt; – This is an open-source hardware initiative that makes electronics accessible as a creative material for everyone. I used it to mock up the circuit designs to make the workshop easier to follow, as you‘ll see below.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Connecting to your Raspberry Pi&lt;/h3&gt;&lt;p&gt;You‘ll need to install Swift on your Raspberry Pi, which I covered in a &lt;a href=&quot;https://lickability.com/blog/swift-on-raspberry-pi/&quot;&gt;previous blog post&lt;/a&gt;. But first, you‘ll need to connect to your Raspberry Pi. You can do this by attaching a monitor, keyboard, and mouse—but it would be very difficult to set up 20 monitors for a workshop, so to work around this, you can connect to the Pi using the USB to TTL serial cable listed above:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/ec5ffefe33ef095255b327bc04383099bbd5c2bc-1148x422.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=287 287w, https://cdn.sanity.io/images/nkt6o869/production/ec5ffefe33ef095255b327bc04383099bbd5c2bc-1148x422.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=574 574w, https://cdn.sanity.io/images/nkt6o869/production/ec5ffefe33ef095255b327bc04383099bbd5c2bc-1148x422.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=861 861w, https://cdn.sanity.io/images/nkt6o869/production/ec5ffefe33ef095255b327bc04383099bbd5c2bc-1148x422.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1148 1148w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/ec5ffefe33ef095255b327bc04383099bbd5c2bc-1148x422.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1148&quot; width=&quot;1148&quot; height=&quot;422&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Step 2&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;ol&gt;&lt;li&gt;Install a &lt;a href=&quot;https://learn.adafruit.com/adafruits-raspberry-pi-lesson-5-using-a-console-cable/software-installation-mac&quot;&gt;driver&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Follow the tutorial found &lt;a href=&quot;https://learn.adafruit.com/adafruits-raspberry-pi-lesson-5-using-a-console-cable/overview&quot;&gt;here&lt;/a&gt; and enter a few commands. I had mixed results, but this is by far the quickest way to connect to a Raspberry Pi without a monitor. Once you‘re connected, your terminal window should look like the image above.&lt;/li&gt;&lt;li&gt;To get your Pi on the network, launch &lt;a href=&quot;https://www.raspberrypi.org/documentation/configuration/raspi-config.md&quot;&gt;raspi-config&lt;/a&gt; and use the prompts to select network options and specify an SSID and password. (If you know the credentials for the WiFi network you‘re connecting to ahead of time, &lt;a href=&quot;https://www.raspberrypi.org/documentation/configuration/wireless/headless.md&quot;&gt;this method&lt;/a&gt; to connect to a “headless” Pi also works very well.)&lt;/li&gt;&lt;li&gt;Enter the command &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;ping raspberrypi.local&lt;/code&gt; to get your Pi‘s IP address to connect to it. For more help, check out the official docs &lt;a href=&quot;https://www.raspberrypi.org/documentation/remote-access/ip-address.md&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Once you have your IP address, you‘re ready to create an SSH session to communicate remotely with your Pi. Type &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;ssh pi@&amp;lt;YOUR_IP_ADDRESS&amp;gt;&lt;/code&gt; into the terminal and select &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;YES&lt;/code&gt; at the next prompt to validate whether or not you trust the connection.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Once you‘re connected to your Raspberry Pi, you’re ready to rock and roll! Now, we can start to build our circuits on the breadboard.&lt;/p&gt;&lt;h3&gt;Building the circuit&lt;/h3&gt;&lt;h4&gt;Parts of the circuit&lt;/h4&gt;&lt;p&gt;To build our circuit, we’ll first need to have a basic understanding of the parts we’ll be using.&lt;/p&gt;&lt;p&gt;The &lt;strong&gt;breadboard&lt;/strong&gt; is the glue that connects all of our components. Mine is broken up into three sections: the two outer sections are our power grids, with &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;+&lt;/code&gt; and &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;−&lt;/code&gt; representing the positive voltage and ground, respectively. The middle section, where our circuit will live, has columns labeled A–J and rows labeled 1–63. Between columns E and F, there is a bridge that splits the middle section in half.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/65226781cde240feb4fa965ace51e24aa4a58c03-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/65226781cde240feb4fa965ace51e24aa4a58c03-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/65226781cde240feb4fa965ace51e24aa4a58c03-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/65226781cde240feb4fa965ace51e24aa4a58c03-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/65226781cde240feb4fa965ace51e24aa4a58c03-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/65226781cde240feb4fa965ace51e24aa4a58c03-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/65226781cde240feb4fa965ace51e24aa4a58c03-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/65226781cde240feb4fa965ace51e24aa4a58c03-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3024 3024w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/65226781cde240feb4fa965ace51e24aa4a58c03-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;2133&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;An example breadboard&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;We‘ll power our circuit through code with a &lt;a href=&quot;https://www.raspberrypi.org/documentation/usage/gpio/README.md&quot;&gt;&lt;strong&gt;General Purpose Input Output&lt;/strong&gt;&lt;/a&gt; pin. GPIO pins can be set to low (0) or high (1)—a majority of pins provide a voltage of 3.3V when set to high.&lt;/p&gt;&lt;p&gt;Our &lt;strong&gt;LED&lt;/strong&gt; is just a &lt;a href=&quot;https://electronics.howstuffworks.com/diode.htm&quot;&gt;semiconductor&lt;/a&gt; that converts electricity into light. We provide the power, and it provides the light. This is a &lt;a href=&quot;https://learn.sparkfun.com/tutorials/polarity/all&quot;&gt;polarized&lt;/a&gt; component, which means we‘ll provide power to one side and ground to the other.&lt;/p&gt;&lt;p&gt;We could connect the LED directly to our power source, but it‘s best practice to protect it from being “burned out” or overpowered by using a &lt;a href=&quot;https://www.explainthatstuff.com/resistors.html&quot;&gt;&lt;strong&gt;resistor&lt;/strong&gt;&lt;/a&gt;, which limits how much current flows through the LED. In my workshop, I used the 220-ohm resistor that came with my LED kit.&lt;/p&gt;&lt;h4&gt;How to build the circuit&lt;/h4&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/24c2faa089c95f485c12a45cc7af9f0d67614268-1755x1134.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/24c2faa089c95f485c12a45cc7af9f0d67614268-1755x1134.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/24c2faa089c95f485c12a45cc7af9f0d67614268-1755x1134.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/24c2faa089c95f485c12a45cc7af9f0d67614268-1755x1134.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/24c2faa089c95f485c12a45cc7af9f0d67614268-1755x1134.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1755 1755w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/24c2faa089c95f485c12a45cc7af9f0d67614268-1755x1134.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1034&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Building our circuit&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;First, provide a ground connection to the breadboard by plugging a jumper cable from GPIO pin #9 on the Raspberry Pi to the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;−&lt;/code&gt; column on the breadboard.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Once you connect a jumper wire from the Raspberry Pi to anywhere on a column of the breadboard, the whole column gets that connection. Notice how the example image above shows green dots going all the way down the breadboard from the black cable—this shows how the ports on the breadboard are connected. In our case, we are providing ground for the whole rail so we can tap into it for all of the circuits we create on the breadboard.&lt;/p&gt;  &lt;/aside&gt;&lt;p&gt;Instead of connecting to the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;+&lt;/code&gt; rail, connect a jumper cable from GPIO 2 (pin #3) to port 16A (row 16, column A) on the breadboard—this will provide power to every port in row 16, shown by the green dots in the example image above.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Notice how the ports F–J in row 16 are not green. This is because the bridge in the middle separates our circuits. If we wanted to connect the other side, we would need to insert a jumper wire from a port in row 16 A–E to a port in row 16 F–J.&lt;/p&gt;  &lt;/aside&gt;&lt;p&gt;Next, insert a &lt;strong&gt;resistor&lt;/strong&gt; into the circuit to limit the flow of current across our LED. Installing our resistor horizontally on row 16 before our bridge will cancel it out—so I’ve installed it vertically, from port 16C to 20C, to continue our connection down to row 20.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;The resistor is not polarized, so it doesn‘t matter what direction we place it in.&lt;/p&gt;  &lt;/aside&gt;&lt;p&gt;Now, connect the &lt;strong&gt;anode&lt;/strong&gt; (the long, positive side of the LED) to port 20D and the &lt;strong&gt;cathode&lt;/strong&gt; (the short, negative side of the LED) to port 21D.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Remember:&lt;/strong&gt; Earlier, we provided a ground connection from our Pi to the ground rail of the breadboard via a jumper cable. This connection completes our circuit—our LED is now grounded by pin #9 and powered by GPIO 2 (pin #3).&lt;/p&gt;&lt;p&gt;To add a second LED: connect a jumper cable from GPIO 3 (pin #5) to port 25S on the breadboard, connect a resistor from 25C to 30C, and connect the LED to the breadboard just like we did before—with the anode connected to the row that the resistor is terminated at, and the cathode connected to the ground rail of the breadboard.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/703c0873543b70b0149a025a800799538c415619-1755x1161.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/703c0873543b70b0149a025a800799538c415619-1755x1161.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/703c0873543b70b0149a025a800799538c415619-1755x1161.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/703c0873543b70b0149a025a800799538c415619-1755x1161.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/703c0873543b70b0149a025a800799538c415619-1755x1161.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1755 1755w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/703c0873543b70b0149a025a800799538c415619-1755x1161.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1058&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Testing your circuit from the command line&lt;/h3&gt;&lt;p&gt;By default, the &lt;a href=&quot;http://wiringpi.com&quot;&gt;WiringPi&lt;/a&gt; library is installed with the Raspbian OS image. This library provides a very easy means of testing your circuit without any configuration or third party software. If, for example, you wanted to test the LED connected to GPIO 2 (pin #3), you could enter the following commands:&lt;/p&gt;&lt;p&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;gpio -g mode 2 output&lt;/code&gt; – Sets GPIO 2 pin as an OUTPUT pin.&lt;/p&gt;&lt;p&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;gpio -g write 2 0&lt;/code&gt; – Sets the GPIO 2 pin LOW or OFF.&lt;/p&gt;&lt;p&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;gpio -g write 2 1&lt;/code&gt; – Sets the GPIO 2 pin HIGH or ON.&lt;/p&gt;&lt;p&gt;We specify the port after the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;mode&lt;/code&gt; keyword or after the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;write&lt;/code&gt; keyword. With that knowledge, you can use the library to test any port you like.&lt;/p&gt;&lt;h3&gt;What‘s next&lt;/h3&gt;&lt;p&gt;In &lt;a href=&quot;https://lickability.com/blog/swift-on-raspberry-pi-workshop-part-2/&quot;&gt;Part 2&lt;/a&gt; of this series, we‘ll create the external build system we need to build and transfer our code from our computer to our Raspberry Pi, and get started writing some Swift code to control our LED.&lt;/p&gt; </content:encoded><author>Marc Aupont</author></item><item><title>How We Do Sales</title><link>https://lickability.com/blog/how-we-do-sales/</link><guid isPermaLink="true">https://lickability.com/blog/how-we-do-sales/</guid><description>Our guide to closing the deal</description><pubDate>Thu, 15 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Part performance art, part science, and part relationship managment—sales is a complicated process, and it can differ drasticly depending on the people involved and the goals of the organization. There is no one silver bullet technique that works for every person, every time.&lt;/p&gt;&lt;p&gt;To hone mine, I’ve relied heavily on learning from others in the industry, analyzing and adopting best practices, and experimenting with new technologies and techniques that others have found success with. Now that I‘ve gotten some experience under my belt, I’d like to share Lickability’s sales process in the hopes it may be a guide to someone else’s success. A rising tide raises all boats, after all. ⛵️&lt;/p&gt;&lt;h3&gt;The Sales Stigma&lt;/h3&gt;&lt;p&gt;When you think of sales, what‘s the first thing that comes to mind? Maybe movies like &lt;em&gt;The Wolf of Wall Street&lt;/em&gt; or &lt;em&gt;Glengarry Glen Ross&lt;/em&gt;, filled with fervent speeches about doing whatever it takes to beat the competition and get someone to &lt;a href=&quot;https://www.youtube.com/watch?v=elrnAl6ygeM&quot;&gt;sign on the line that is dotted&lt;/a&gt;? Or maybe comedy accounts like &lt;em&gt;Corporate Bro&lt;/em&gt;, depicting the daily grind of “slanging software,” the humor and horror of cold calling or emailing, all while preaching the &lt;a href=&quot;https://www.youtube.com/watch?v=kc2YoauO3f0&quot;&gt;mantra of S.A.D.N.E.S.S.&lt;/a&gt; (Sales Are Dope Never Ever Stop Selling), a favorite of mine.&lt;/p&gt;&lt;p&gt;I wasn’t always acutely aware of all the stereotypes and stigmas surrounding the profession and those who practice it. Sure, I’ve heard the warnings of pushy car salespeople who pressure you into buying something—but when I went to buy a car after landing my first job out of college, it was a pleasant, straightforward process. Even in the context of high-dollar corporate sales, when I was an engineer in my previous role, all of my interactions with sales professionals were generally easy and informative experiences.&lt;/p&gt;&lt;p&gt;When I joined Lickability as an account manager, I quickly learned that not everyone sees sales professionals in the positive light that I do. In the beginning of my career, I was hesitant and a little embarrassed to tell people I worked in sales, afraid that they would see me as the stereotypical pushy, aggressive salesperson. But after having spent some time practicing with and learning from others in the industry, I’ve realized that sales isn’t actually about closing at all costs—it’s really about helping people, and that‘s nothing to be ashamed of.&lt;/p&gt;&lt;h3&gt;How We’re Different&lt;/h3&gt;&lt;p&gt;Our two-person sales team at Lickability enjoys and jokingly uses some sales memes from time to time, but we choose to operate outside of those common stereotypes to give our prospects and clients the best experience possible. To us, this means a low- or no-pressure experience, especially in the early stages of a deal’s progress; being honest about what we can deliver, instead of making promises we can’t keep; and giving a potential customer as much information as possible to help them make the right decisions.&lt;/p&gt;&lt;p&gt;The way we act and communicate with clients is driven by the way we view our sales process: we look at each potential deal as a true partnership, so when we work together, we’re in it for the long haul. For example, every communication a client receives from us has a personal touch. While we do have some email templates for frequently asked questions or scheduling meetings—and we‘ve leveraged AI like &lt;a href=&quot;https://openai.com/blog/chatgpt&quot;&gt;ChatGPT&lt;/a&gt; to help generate creative ideas for emails and social media posts — we mostly use them as a base and customize them to each individual client and situation.&lt;/p&gt;&lt;p&gt;The partnerships we strive for have to be a two-way street—we want both of us to succeed. When we meet a client for the first time, we pay attention to see if we will work well together and believe in what we’re going to build. We also take the happiness of our engineers into account: will they enjoy the project, will they learn something new doing this, are the project expectations clearly defined? While we’d like to work with every client we meet, sometimes it’s not a good fit, and bringing that up as early in the process as possible is a key point in keeping sales honest.&lt;/p&gt;&lt;h3&gt;How We Sell&lt;/h3&gt;&lt;p&gt;Our sales process is constantly changing as our company evolves, but here is how we currently operate:&lt;/p&gt;&lt;h4&gt;Lead generation&lt;/h4&gt;&lt;p&gt;Our process starts with lead generation, both inbound and outbound. For inbound, we rely on referrals from previous clients, visits to our website and blog, and our own personal networks. For outboud, we use cold emails, social media posts, and cold outreach (Text, Twitter DM, Slack DM) to others in the development community. We also sometimes run &lt;a href=&quot;https://twitter.com/lickability/status/1662131556616372224?s=20&quot;&gt;ads on our favorite podcasts&lt;/a&gt;!&lt;/p&gt;&lt;h4&gt;Initial discovery call&lt;/h4&gt;&lt;p&gt;When a lead comes in, we usually begin with a 30 minute discovery call. This gives us a chance to make introductions, get to know each other a bit, and discuss goals and scope. We always communicate clear action items afterwards, whether that’s a client sending us information to review or for us to get a proposal together.&lt;/p&gt;&lt;h4&gt;Estimate&lt;/h4&gt;&lt;p&gt;A typical next step is for the sales team to bring the information we gathered from our initial client meeting to our product design and engineering teams, in order for them to give an estimate on the work. The estimation includes figuring out what kind of team we want to put together for your project and how many weeks we‘ll work on it.&lt;/p&gt;&lt;h4&gt;Proposal&lt;/h4&gt;&lt;p&gt;When the estimation is complete, the sales team takes the information provided by engineering and writes a full proposal that we present to the client, typically over Zoom. We like to do this so that we can explain our thought process, point out assumptions, and answer any immediate questions the client may have.&lt;/p&gt;&lt;h4&gt;Negotiation&lt;/h4&gt;&lt;p&gt;After the client has had some time to review the proposal, we continue discussing and adjusting until everyone is satisfied with the scope of the project. From there, we create a Statement of Work and begin the contract negotiation process. During this process we also review the terms of our full Services Agreement, which covers everything from the hours we work, confidentiality, ownership, payment terms, etc.&lt;/p&gt;&lt;h4&gt;Closing&lt;/h4&gt;&lt;p&gt;When negotiations have finished, we finalize all the paperwork and send it to the client for electronic signature via Docusign. If all goes well, the client will sign and we’ve closed the deal and begin a project!&lt;/p&gt;&lt;h4&gt;Onboarding&lt;/h4&gt;&lt;p&gt;After the ink is dry, sales begins the onboarding process. The first step is sending out some onboarding forms so that we can get logistical information (such as meeting times, slack access, and invoicing) and engineering information (style guides, PR process). Sales takes this information and begins syncing up Lickability’s project team with the client‘s team. We also schedule a project kickoff to make face to face introductions and officially begin the project.&lt;/p&gt;&lt;h4&gt;Keeping up the relationship&lt;/h4&gt;&lt;p&gt;As the project progresses, sales takes a bit of a back seat so Lickability‘s designated project lead can run the show. But we keep track of how the project is coming along, help with invoicing, and remain available as a familiar point of contact for the client. We also keep an eye on the progress of the project and check in with the client periodically about project extensions, additional projects, or maintenance contracts.&lt;/p&gt;&lt;h3&gt;How We Learn&lt;/h3&gt;&lt;p&gt;At Lickability we are constantly learning and trying to get better at our jobs by hosting cross-team learning sessions, evaluating new software tools, and attending or speaking at conferences. We are always looking toward our community and industry for ways we can be better. From iOS developer communities, shared Slack channels with fellow agencies, friends and colleagues, our own &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;#everybody&lt;/code&gt; channel, we aren’t afraid to ask for help and learn what we can do better.&lt;/p&gt;&lt;h3&gt;Work With Us&lt;/h3&gt;&lt;p&gt;So that’s our sales process in essence. If you’d like to share some of your own experiences and advice, or just talk shop and see if we can learn from each other, hit me up on &lt;a href=&quot;https://twitter.com/ThomasDeVuono&quot;&gt;Twitter&lt;/a&gt;. And if you are thinking about your next software project and all this sounds like your ideal experience, then great! &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Let’s get the conversation started.&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;This article has been updated as of June 1 2023&lt;/em&gt;&lt;/p&gt; </content:encoded><author>Thomas DeVuono</author></item><item><title>Meet Buildwatch</title><link>https://lickability.com/blog/meet-buildwatch/</link><guid isPermaLink="true">https://lickability.com/blog/meet-buildwatch/</guid><description>A macOS app for developers</description><pubDate>Tue, 29 Sep 2020 00:00:00 GMT</pubDate><content:encoded>&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e7ebca837dbd5e104759e5cdb0846a7b74e08ebe-1024x1024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/e7ebca837dbd5e104759e5cdb0846a7b74e08ebe-1024x1024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/e7ebca837dbd5e104759e5cdb0846a7b74e08ebe-1024x1024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/e7ebca837dbd5e104759e5cdb0846a7b74e08ebe-1024x1024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e7ebca837dbd5e104759e5cdb0846a7b74e08ebe-1024x1024.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;After a decade of making software, we‘re releasing our first paid developer tool today on the Mac App Store. Say hello to &lt;a href=&quot;https://buildwatch.app&quot;&gt;Buildwatch&lt;/a&gt;—a menu bar app that keeps an eye on your compile times throughout the day.&lt;/p&gt;&lt;p&gt;We‘ve been building apps in Xcode for years, and we wanted a tool that would show us how long all that building takes over time. That tool didn’t exist, so we did what we do best: we made our own. Buildwatch shows you, in just a glance, how much time you‘ve spent building in Xcode each day and how many times you’ve built your apps. We designed and developed it exclusively for macOS with support for power users, a focus on privacy, and plenty of exciting things to come in future releases.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6d4a7f2396c12026f6b7ceda5ed743bceb11a2a8-1400x900.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=350 350w, https://cdn.sanity.io/images/nkt6o869/production/6d4a7f2396c12026f6b7ceda5ed743bceb11a2a8-1400x900.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=700 700w, https://cdn.sanity.io/images/nkt6o869/production/6d4a7f2396c12026f6b7ceda5ed743bceb11a2a8-1400x900.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1050 1050w, https://cdn.sanity.io/images/nkt6o869/production/6d4a7f2396c12026f6b7ceda5ed743bceb11a2a8-1400x900.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1400 1400w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6d4a7f2396c12026f6b7ceda5ed743bceb11a2a8-1400x900.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1400&quot; width=&quot;1400&quot; height=&quot;900&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Clicking the watch icon in your menu bar reveals a graph that breaks down your hour, day, or week by how much time you‘ve spent compiling your projects. Hover over any segment for a deeper look at the data behind it. Opening the “More info” window gives you even more control to break down your stats by totals, averages, and schemes. Plus, you can inspect your build times for the month, the year, and all time.&lt;/p&gt;&lt;p&gt;We hope Buildwatch gives developers more insight into how their time and resources are being used throughout the app development process, helping you make decisions about equipment and fix bottlenecks. If you develop apps for Apple platforms, we think you‘ll love our menu bar utility as much as we do.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/3bc9c58c4a63a8307158890768b7623596802c30-1400x900.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=350 350w, https://cdn.sanity.io/images/nkt6o869/production/3bc9c58c4a63a8307158890768b7623596802c30-1400x900.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=700 700w, https://cdn.sanity.io/images/nkt6o869/production/3bc9c58c4a63a8307158890768b7623596802c30-1400x900.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1050 1050w, https://cdn.sanity.io/images/nkt6o869/production/3bc9c58c4a63a8307158890768b7623596802c30-1400x900.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1400 1400w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/3bc9c58c4a63a8307158890768b7623596802c30-1400x900.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1400&quot; width=&quot;1400&quot; height=&quot;900&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Thank you to all of our beta testers for their feedback and support while Buildwatch came to life, and a special thanks to &lt;a href=&quot;https://twitter.com/pearapps&quot;&gt;Ken Ackerson&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/samhenrigold&quot;&gt;Sam Gold&lt;/a&gt; for partnering with us on development and design.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;&lt;strong&gt;Buildwatch is available now on the &lt;a href=&quot;https://apps.apple.com/app/apple-store/id1523347474?pt=274196&amp;ct=lckblog&amp;mt=8&quot;&gt;Mac App Store&lt;/a&gt; for $9.99&lt;/strong&gt;. Find out more at &lt;a href=&quot;https://buildwatch.app&quot;&gt;buildwatch.app&lt;/a&gt;. Once you try it out, we‘d love to know what you think! Leave us a review in the App Store or send us suggestions on Twitter at &lt;a href=&quot;https://twitter.com/buildwatchapp&quot;&gt;@buildwatchapp&lt;/a&gt;.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Team Lickability</author></item><item><title>Learning with Lickability</title><link>https://lickability.com/blog/learning-with-lickability/</link><guid isPermaLink="true">https://lickability.com/blog/learning-with-lickability/</guid><description>All about our weekly learning sessions</description><pubDate>Fri, 04 Sep 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every Wednesday morning, we congregate on a Screen call for an hour—not to have a meeting that really could have been an email, but to learn. We call it &lt;em&gt;Learn with Lickability&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Learn with Lickability is our weekly dedicated time for one of us to teach the rest of the team about something interesting, whether it‘s related to iOS development or not. Sometimes it involves live demonstrations of fun things you can do with &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Stack​View&lt;/code&gt;, and sometimes it‘s a detailed look into our process for finding and switching to a new business bank account. I once did a presentation about different methods of project management, and I’m planning an upcoming session about how to use Notion. Any topic is fair game. Here are a few of the topics we‘ve covered recently:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;UI​Stack​View&lt;/code&gt; / Auto Layout&lt;/li&gt;&lt;li&gt;Practical Combine&lt;/li&gt;&lt;li&gt;Swift Naming&lt;/li&gt;&lt;li&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;AV​Asset​Resource​Loader​Delegate&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Generics / Associated Types&lt;/li&gt;&lt;li&gt;Property Wrappers&lt;/li&gt;&lt;li&gt;SwiftLint&lt;/li&gt;&lt;li&gt;Debugging&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Why do we do it?&lt;/h3&gt;&lt;p&gt;What‘s the point of taking time out of everyone’s day for something like this? Sure, maybe that hour a week would be better used for something else. And maybe the time it takes to actually prepare for a talk is time that we “should” be using to build apps. So, why do we do it?&lt;/p&gt;&lt;p&gt;Learn with Lickability gives us structured time to expose ourselves to unfamiliar topics, learn new things, and practice teaching others. Because it‘s completely optional to either attend or lead a session, we can be certain that the topics we’re covering are interesting and important to both the person teaching and the people learning—which means it‘s well worth our time. A love for learning may not be one of the &lt;a href=&quot;https://lickability.com/blog/the-value-of-values/&quot;&gt;five flavors of Lickability&lt;/a&gt;, but it‘s pretty close.&lt;/p&gt;&lt;p&gt;Learn with Lickability is also a great excuse to come together as a team for something a little more fun than the average standup or client meeting. With everyone working remotely, it‘s hard to replicate the atmosphere of having casual chats in the office together, and there are less opportunities for us to feel like a team. Learn with Lickability, along with virtual game nights and team lunch Google Meet calls, is one of the ways we’ve been able to stay connected from afar.&lt;/p&gt;&lt;h3&gt;How do we do it?&lt;/h3&gt;&lt;p&gt;We schedule future Learn with Lickability topics a few weeks in advance so people have time to prepare their presentations on &lt;a href=&quot;https://lickability.com/blog/the-magic-of-the-everybody-meeting/&quot;&gt;Lickability Fridays&lt;/a&gt;. On the morning of, we post that Wednesday‘s topic in our #learn-with-lickability Slack channel, along with a link to the video chat.&lt;/p&gt;&lt;p&gt;We‘ve been using &lt;a href=&quot;https://screen.so/&quot;&gt;Screen&lt;/a&gt; for Learn with Lickability because it‘s great for screen sharing presentations or demos. The only downside is that we can’t add a link to the call in our weekly calendar event, but the Slack integration makes it easy to start or join a call right from the #learn-with-lickability channel.&lt;/p&gt;&lt;p&gt;Learning new things and teaching others should be an integral part of your work, even and especially if you‘re working remotely. I look forward to Learn with Lickability every week, no matter what the topic is—because watcing a live demo or listening to someone explain something they find interesting is such a joy.&lt;/p&gt;&lt;p&gt;If you do something like this at your company—or if you want to start, which we highly recommend—let us know how it goes! We‘d love to hear from you.&lt;/p&gt; </content:encoded><author>Jillian Meehan</author></item><item><title>Conferences Condensed: WWDC20</title><link>https://lickability.com/blog/conferences-condensed-wwdc20/</link><guid isPermaLink="true">https://lickability.com/blog/conferences-condensed-wwdc20/</guid><description>Our favorite sessions from this year</description><pubDate>Thu, 09 Jul 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We look forward to WWDC every year, and we know you do, too. As much as we missed seeing all of our pals in San Jose for the conference, we still had a great time watching sessions online and chatting with familiar faces and new friends in our &lt;a href=&quot;https://discord.gg/QZCgAxx&quot;&gt;Discord&lt;/a&gt;. Keep reading to hear a bit about some of our favorite videos this year.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2020/10097/&quot;&gt;Advances in UICollectionView&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;So much of what we do on a daily basis involves the meticulous formatting of data to look great in performant scrolling lists. Several of this year‘s WWDC sessions focused on new technologies that make that possible in both &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Kit&lt;/code&gt; and &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Swift​UI&lt;/code&gt;, and I couldn‘t be happier. &lt;strong&gt;Advances in UICollectionView&lt;/strong&gt; provides a great overview of the new technologies that enable enhancements to lists in &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;UI​Kit&lt;/code&gt;. Hello &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​List​Cell&lt;/code&gt; and goodbye &lt;code index=&quot;11&quot; isInline=&quot;true&quot;&gt;UI​Table​View&lt;/code&gt;. You‘ll definitely want to bookmark all of the 2020 sessions referenced in the session for a deeper dive: &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2020/10045&quot;&gt;Advances in diffable data sources&lt;/a&gt;, &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2020/10026&quot;&gt;Lists in UICollectionView&lt;/a&gt;, and &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2020/10027&quot;&gt;Modern cell configuration&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;— &lt;em&gt;Michael Liberatore&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2020/10649/&quot;&gt;Add custom views and modifiers to the Xcode Library&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;The promise of adding custom views to the Xcode Library goes &lt;em&gt;way&lt;/em&gt; back to Xcode 3 with &lt;a href=&quot;https://www.cocoawithlove.com/2009/07/custom-views-in-interface-builder-using.html&quot;&gt;Interface Builder plugins&lt;/a&gt;. But this year, Apple brought that idea to SwiftUI via two new protocols called &lt;a href=&quot;https://developer.apple.com/documentation/developertoolssupport/librarycontentprovider&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Library​Content​Provider&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.apple.com/documentation/developertoolssupport/libraryitem&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Library​Item&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;No longer will we toil trying to get &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;@IB​Inspectable&lt;/code&gt; to work as intended. Now it’s so simple to create a new SwiftUI view and have it show up with sample data right inside the Xcode Library. But Apple didn’t stop at views—they even made it possible to expose custom Modifiers as well. As Anton, the talk’s presenter, put it, this will allow us to discover and learn about all the controls in our apps‘ design systems much more easily and visually than just reading the source code.&lt;/p&gt;&lt;p&gt;— &lt;em&gt;mb Bischoff&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2020/10676/&quot;&gt;Build trust through better privacy&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;This was a great presentation on how Apple handles user privacy. The talk goes over the four key pillars of privacy, what they mean, and how you can implement those best practices into your app to build trust with your users. Here are a few of my takeaways:&lt;/p&gt;&lt;h4&gt;On-device processing&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;When user data is sent off-device for processing, a user loses control over that data&lt;/li&gt;&lt;li&gt;Apple is continuing to improve Core ML to integrate machine learning models into your app, to keep processing on-device rather than on a server&lt;/li&gt;&lt;li&gt;Taking advantage of the tools Apple provides is a good way to minimize the data that is processed off-device&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Data minimization&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;We should aim to show respect for a user‘s data and ask for as little of it as possible—only ask for what you need&lt;/li&gt;&lt;li&gt;Again, Apple is providing new tools to make this easier&lt;ul&gt;&lt;li&gt;Limited Photos Library: a user can define which photos they want to share, instead of sharing all of their photos&lt;/li&gt;&lt;li&gt;Share approximate location, rather than exact location&lt;/li&gt;&lt;li&gt;QuickType to suggest contact details, rather than asking for access to all user contacts&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;Limited access doesn‘t mean limited functionality&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Security protections&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Apple is adding Encrypted DNS protocols that are natively supported&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Transparency and control&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Put users in control of their data&lt;/li&gt;&lt;li&gt;Inform users about when, how, and why their data is being collected&lt;ul&gt;&lt;li&gt;Updates to the App Store Product Page can let users easily see what data an app collects before downloading&lt;/li&gt;&lt;li&gt;You also need to declare this for any third-party SDKs you use&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;— &lt;em&gt;Tom DeVuono&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2020/10165/&quot;&gt;Embrace Swift type inference&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Leveraging type inference is a fundamental way to learn about generics in Swift. Often we can overlook ways to compose objects in a large project—type inference allows us to omit explicit type annotations without compromising type safety. This is also why we can create complex interfaces within SwiftUI with just a few lines of code, since it relies heavily on type inference at the call sites.&lt;/p&gt;&lt;p&gt;I really appreciated the speaker’s cadence in this talk, since this concept dives into how the Swift compiler infers concrete types without explicitly specifying them.&lt;/p&gt;&lt;p&gt;— &lt;em&gt;Daisy Ramos&lt;/em&gt;&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Installing the macOS Big Sur beta on a new partition</title><link>https://lickability.com/blog/installing-macos-big-sur-on-a-new-partition/</link><guid isPermaLink="true">https://lickability.com/blog/installing-macos-big-sur-on-a-new-partition/</guid><description>Our step-by-step guide</description><pubDate>Mon, 22 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Big Sur has just been announced, and you‘re itching to give it a shot. But wait—you have plenty of apps to support and work to do. Is it worth the risk of things not working or crashing? Probably not. In this blog post, we are going to look into how to install the latest OS without disrupting your current setup.&lt;/p&gt;&lt;h3&gt;Installing the profile&lt;/h3&gt;&lt;p&gt;The first step is to go to the &lt;a href=&quot;https://developer.apple.com&quot;&gt;Apple Developer Portal&lt;/a&gt; and download the &lt;strong&gt;macOS Big Sur beta&lt;/strong&gt; profile. Installing the profile will give us the ability to download the latest OS and put it on our hard disk of choice.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/75061f489927ad654a32b2140b464b21957ce7bd-1974x636.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/75061f489927ad654a32b2140b464b21957ce7bd-1974x636.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/75061f489927ad654a32b2140b464b21957ce7bd-1974x636.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/75061f489927ad654a32b2140b464b21957ce7bd-1974x636.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/75061f489927ad654a32b2140b464b21957ce7bd-1974x636.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1974 1974w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/75061f489927ad654a32b2140b464b21957ce7bd-1974x636.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;516&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;The download is approximately 9.56GB in size, so be sure you have enough space for it first.&lt;/p&gt;  &lt;/aside&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c54d2e447fedfb1b6eae4d5d2531caf9d34195c3-1338x572.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=335 335w, https://cdn.sanity.io/images/nkt6o869/production/c54d2e447fedfb1b6eae4d5d2531caf9d34195c3-1338x572.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=669 669w, https://cdn.sanity.io/images/nkt6o869/production/c54d2e447fedfb1b6eae4d5d2531caf9d34195c3-1338x572.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1004 1004w, https://cdn.sanity.io/images/nkt6o869/production/c54d2e447fedfb1b6eae4d5d2531caf9d34195c3-1338x572.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1338 1338w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c54d2e447fedfb1b6eae4d5d2531caf9d34195c3-1338x572.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1338&quot; width=&quot;1338&quot; height=&quot;572&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Once the download is completed, we‘re ready to prepare the partition where we will install it.&lt;/p&gt;&lt;h3&gt;Creating a new partition&lt;/h3&gt;&lt;p&gt;There are two potential ways to do this: creating a new volume, or creating a new partition. That said, there seems to be a small catch with creating a new volume, as seen in the tweet below, so we are going down the path of creating a new partition because of the potential upgrade issues.&lt;/p&gt;&lt;blockquote&gt;This caveat is buried deeply enough in the macOS Big Sur release notes that a lot of people are going to be bit by it. Creating a new volume in an existing APFS container had become the de facto best way to install a second OS. &lt;a href=&quot;https://t.co/BscbELxj6Q&quot;&gt;pic.twitter.com/BscbELxj6Q&lt;/a&gt;&lt;br&gt;&lt;br&gt;— Daniel Jalkut (@danielpunkass) &lt;a href=&quot;https://twitter.com/danielpunkass/status/1275270026648526848?ref_src=twsrc%5Etfw&quot;&gt;June 23, 2020&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;The purpose of creating a new partition is so we can install the beta on our machine without disturbing our current OS and setup. As exciting as it may be to try out the latest OS, it‘s still in beta and can have unexpected results. By creating a new partition, we are creating a separate “bubble” to work in that doesn’t affect the current bubble that we use for work.&lt;/p&gt;&lt;p&gt;To create a new partition, launch &lt;strong&gt;Disk Utility&lt;/strong&gt; and select the &lt;strong&gt;Partition&lt;/strong&gt; button. From here we can press the &lt;strong&gt;+&lt;/strong&gt; button to create a new partition&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/eebb309941cfdd563b265c70862272fdba538230-2076x1366.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/eebb309941cfdd563b265c70862272fdba538230-2076x1366.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/eebb309941cfdd563b265c70862272fdba538230-2076x1366.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/eebb309941cfdd563b265c70862272fdba538230-2076x1366.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/eebb309941cfdd563b265c70862272fdba538230-2076x1366.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/eebb309941cfdd563b265c70862272fdba538230-2076x1366.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2076 2076w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/eebb309941cfdd563b265c70862272fdba538230-2076x1366.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1053&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;After that, all we need to do is provide a name and size for our partition. In the example below, we used the name &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Macintosh HD Beta&lt;/code&gt; and specified a size of 100GB for the partition.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a6bdc82f0dbc25f70a4cad5eb0f6b0143670ca16-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/a6bdc82f0dbc25f70a4cad5eb0f6b0143670ca16-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/a6bdc82f0dbc25f70a4cad5eb0f6b0143670ca16-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/a6bdc82f0dbc25f70a4cad5eb0f6b0143670ca16-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/a6bdc82f0dbc25f70a4cad5eb0f6b0143670ca16-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/a6bdc82f0dbc25f70a4cad5eb0f6b0143670ca16-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2076 2076w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a6bdc82f0dbc25f70a4cad5eb0f6b0143670ca16-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1082&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Press the + button to add a partition.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f37bdfd4d3f321885dfdcc5f84a6c3096d8fa798-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/f37bdfd4d3f321885dfdcc5f84a6c3096d8fa798-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/f37bdfd4d3f321885dfdcc5f84a6c3096d8fa798-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/f37bdfd4d3f321885dfdcc5f84a6c3096d8fa798-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/f37bdfd4d3f321885dfdcc5f84a6c3096d8fa798-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/f37bdfd4d3f321885dfdcc5f84a6c3096d8fa798-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2076 2076w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f37bdfd4d3f321885dfdcc5f84a6c3096d8fa798-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1082&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Specify the size and name of your partition and select &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;apply&lt;/code&gt;. &lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b8aa761ab152f1a82c66eaa943ca7599acebecc6-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/b8aa761ab152f1a82c66eaa943ca7599acebecc6-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/b8aa761ab152f1a82c66eaa943ca7599acebecc6-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/b8aa761ab152f1a82c66eaa943ca7599acebecc6-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/b8aa761ab152f1a82c66eaa943ca7599acebecc6-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/b8aa761ab152f1a82c66eaa943ca7599acebecc6-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2076 2076w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b8aa761ab152f1a82c66eaa943ca7599acebecc6-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1082&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;This alert shows you which partition will be added, and which partition will shrink to make room for it.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e133c88fda69878c1bce65f17f49b4fb8932bee6-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/e133c88fda69878c1bce65f17f49b4fb8932bee6-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/e133c88fda69878c1bce65f17f49b4fb8932bee6-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/e133c88fda69878c1bce65f17f49b4fb8932bee6-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/e133c88fda69878c1bce65f17f49b4fb8932bee6-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/e133c88fda69878c1bce65f17f49b4fb8932bee6-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2076 2076w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e133c88fda69878c1bce65f17f49b4fb8932bee6-2076x1404.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1082&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Select &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;continue&lt;/code&gt; to begin partitioning your hard drive.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Once we apply the changes, our partitions will be resized in order to accommodate all partitions within our disk.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c507e42ba16ef61df7041ebbd24751e84d412c4b-2076x1366.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/c507e42ba16ef61df7041ebbd24751e84d412c4b-2076x1366.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/c507e42ba16ef61df7041ebbd24751e84d412c4b-2076x1366.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/c507e42ba16ef61df7041ebbd24751e84d412c4b-2076x1366.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/c507e42ba16ef61df7041ebbd24751e84d412c4b-2076x1366.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/c507e42ba16ef61df7041ebbd24751e84d412c4b-2076x1366.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2076 2076w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c507e42ba16ef61df7041ebbd24751e84d412c4b-2076x1366.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1053&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Once the partition has been added, it will show up in the menu on the left, as you can see in the screenshot below.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/fc9af2a32911b76039e68d2cca62dd67fce39d53-1856x1144.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/fc9af2a32911b76039e68d2cca62dd67fce39d53-1856x1144.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/fc9af2a32911b76039e68d2cca62dd67fce39d53-1856x1144.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/fc9af2a32911b76039e68d2cca62dd67fce39d53-1856x1144.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/fc9af2a32911b76039e68d2cca62dd67fce39d53-1856x1144.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1856 1856w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/fc9af2a32911b76039e68d2cca62dd67fce39d53-1856x1144.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;986&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Installing Big Sur&lt;/h3&gt;&lt;p&gt;Now that we have our new partition created, we‘re ready to install macOS Big Sur. At this point in the installation process, you should be at the screen where you need to select which hard drive to install on. This is where we select our newly created &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Macintosh HD Beta&lt;/code&gt; partition. To complete the install, perform the following steps:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Select the &lt;strong&gt;Show All Disks&lt;/strong&gt; button&lt;/li&gt;&lt;li&gt;Select &lt;strong&gt;Macintosh HD Beta&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;Select &lt;strong&gt;Install&lt;/strong&gt;&lt;/li&gt;&lt;/ol&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/50f236a06e5785bbd057c4564ec85cb7d38eb2e9-1596x1240.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=399 399w, https://cdn.sanity.io/images/nkt6o869/production/50f236a06e5785bbd057c4564ec85cb7d38eb2e9-1596x1240.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=798 798w, https://cdn.sanity.io/images/nkt6o869/production/50f236a06e5785bbd057c4564ec85cb7d38eb2e9-1596x1240.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1197 1197w, https://cdn.sanity.io/images/nkt6o869/production/50f236a06e5785bbd057c4564ec85cb7d38eb2e9-1596x1240.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1596 1596w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/50f236a06e5785bbd057c4564ec85cb7d38eb2e9-1596x1240.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1596&quot; width=&quot;1596&quot; height=&quot;1240&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Step 1&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/3fb1e58e7a2258be27fef27f60e7c1bc51f917cb-1598x1242.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/3fb1e58e7a2258be27fef27f60e7c1bc51f917cb-1598x1242.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=799 799w, https://cdn.sanity.io/images/nkt6o869/production/3fb1e58e7a2258be27fef27f60e7c1bc51f917cb-1598x1242.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1199 1199w, https://cdn.sanity.io/images/nkt6o869/production/3fb1e58e7a2258be27fef27f60e7c1bc51f917cb-1598x1242.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1598 1598w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/3fb1e58e7a2258be27fef27f60e7c1bc51f917cb-1598x1242.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1598&quot; width=&quot;1598&quot; height=&quot;1242&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Step 2&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a18e85249b4977333c2130b88bc9ad6239b62ef2-1598x1240.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/a18e85249b4977333c2130b88bc9ad6239b62ef2-1598x1240.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=799 799w, https://cdn.sanity.io/images/nkt6o869/production/a18e85249b4977333c2130b88bc9ad6239b62ef2-1598x1240.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1199 1199w, https://cdn.sanity.io/images/nkt6o869/production/a18e85249b4977333c2130b88bc9ad6239b62ef2-1598x1240.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1598 1598w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a18e85249b4977333c2130b88bc9ad6239b62ef2-1598x1240.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1598&quot; width=&quot;1598&quot; height=&quot;1240&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Step 3&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;That‘s it! Once the installation process is complete, you’ll be able to run Big Sur on the new partition.&lt;/p&gt;&lt;h3&gt;Booting into macOS Big Sur&lt;/h3&gt;&lt;p&gt;To boot to your new partition, perform the following steps:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Shut down your Mac&lt;/li&gt;&lt;li&gt;Press and hold the &lt;strong&gt;option&lt;/strong&gt; key while powering up&lt;/li&gt;&lt;li&gt;Select the partition you want to boot from&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;If you want to automatically boot into your new macOS Big Sur installation, you can set this in &lt;strong&gt;System Preferences&lt;/strong&gt; via the &lt;strong&gt;Startup Disk&lt;/strong&gt;. Once this is set, your Mac will automatically boot into whichever option you selected when you restart.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b9dc3315c28bacb6d0949a68a414f7a2a810fda8-1336x808.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=334 334w, https://cdn.sanity.io/images/nkt6o869/production/b9dc3315c28bacb6d0949a68a414f7a2a810fda8-1336x808.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=668 668w, https://cdn.sanity.io/images/nkt6o869/production/b9dc3315c28bacb6d0949a68a414f7a2a810fda8-1336x808.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1002 1002w, https://cdn.sanity.io/images/nkt6o869/production/b9dc3315c28bacb6d0949a68a414f7a2a810fda8-1336x808.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1336 1336w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b9dc3315c28bacb6d0949a68a414f7a2a810fda8-1336x808.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1336&quot; width=&quot;1336&quot; height=&quot;808&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;When you switch back to your Catalina partition, you may see an “Incompatible Disk” alert that looks like &lt;a href=&quot;https://twitter.com/caseyliss/status/1277041351935307779&quot;&gt;this&lt;/a&gt;. All it means is that your Catalina partition is seeing the Big Sur partition and doesn‘t understand it. It won’t cause problems for either OS.&lt;/p&gt;&lt;h3&gt;Removing the beta profile&lt;/h3&gt;&lt;p&gt;Once you‘ve installed the beta profile to download the OS, you probably don’t need it on your main machine anymore. To remove the profile, perform the following steps:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Go into &lt;strong&gt;System Preferences&lt;/strong&gt; and select &lt;strong&gt;Software Update&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;From there select &lt;strong&gt;details&lt;/strong&gt; underneath the Software Update&lt;/li&gt;&lt;li&gt;Once selected, you‘ll be able to select &lt;strong&gt;Restore Defaults,&lt;/strong&gt; which will restore your computer‘s regular update cycle. When you want to install new beta updates, you can do that from your new Big Sur volume instead.&lt;/li&gt;&lt;/ol&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b664c889c81ffdc1bc6b2e5658565462bc531835-1338x620.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=335 335w, https://cdn.sanity.io/images/nkt6o869/production/b664c889c81ffdc1bc6b2e5658565462bc531835-1338x620.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=669 669w, https://cdn.sanity.io/images/nkt6o869/production/b664c889c81ffdc1bc6b2e5658565462bc531835-1338x620.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1004 1004w, https://cdn.sanity.io/images/nkt6o869/production/b664c889c81ffdc1bc6b2e5658565462bc531835-1338x620.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1338 1338w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b664c889c81ffdc1bc6b2e5658565462bc531835-1338x620.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1338&quot; width=&quot;1338&quot; height=&quot;620&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/efecacf71ec2fd2ce0d194df29633b83ec19b3cb-1338x628.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=335 335w, https://cdn.sanity.io/images/nkt6o869/production/efecacf71ec2fd2ce0d194df29633b83ec19b3cb-1338x628.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=669 669w, https://cdn.sanity.io/images/nkt6o869/production/efecacf71ec2fd2ce0d194df29633b83ec19b3cb-1338x628.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1004 1004w, https://cdn.sanity.io/images/nkt6o869/production/efecacf71ec2fd2ce0d194df29633b83ec19b3cb-1338x628.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1338 1338w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/efecacf71ec2fd2ce0d194df29633b83ec19b3cb-1338x628.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1338&quot; width=&quot;1338&quot; height=&quot;628&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Summary&lt;/h3&gt;&lt;p&gt;That wraps it up! At this point, you should have everything necessary to play around with the beta while maintaining the normal state of your machine.&lt;/p&gt;&lt;p&gt;If you‘d like to see a quick video of the whole process, check it out below!&lt;/p&gt;&lt;p&gt;And if you‘re interested in building some Mac apps, &lt;a href=&quot;https://lickability.com/contact&quot;&gt;get in touch&lt;/a&gt;! We‘d be happy to help.&lt;/p&gt; </content:encoded><author>Marc Aupont</author></item><item><title>Getting Started with UI​Collection​View​Compositional​Layout</title><link>https://lickability.com/blog/getting-started-with-uicollectionviewcompositionallayout/</link><guid isPermaLink="true">https://lickability.com/blog/getting-started-with-uicollectionviewcompositionallayout/</guid><description>A guide to building complex layouts</description><pubDate>Wed, 17 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At WWDC 2019, Apple &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/215/&quot;&gt;introduced&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/lickability/status/1246091526041669632&quot;&gt;later documented&lt;/a&gt; an unbelievable API for building complex layouts with ease. &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewcompositionallayout&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Compositional​Layout&lt;/code&gt;&lt;/a&gt; promised to simplify collection view layouts using a more declarative approach without the need to subclass to achieve customization—and it delivered. Our team has been making use of compositional layouts ever since, and we’d like to help you get started as well.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;We made an Xcode project with all of the examples shown in this post. You can find it right &lt;a href=&quot;https://github.com/Lickability/collection-view-compositional-layout-demo&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;  &lt;/aside&gt;&lt;h3&gt;Back to Basics&lt;/h3&gt;&lt;p&gt;For years, &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewflowlayout&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Flow​Layout&lt;/code&gt;&lt;/a&gt; , a &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewlayout&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Layout&lt;/code&gt;&lt;/a&gt; subclass, helped us to achieve simple line-based layouts with little configuration, and with little customization required to perfectly create a common grid layout. More advanced customization often meant subclassing &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Flow​Layout&lt;/code&gt;, or creating your own layout by subclassing &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Layout&lt;/code&gt; directly. We’ll start by reviewing how to build a grid with a flow layout, and then show you how to achieve the same design using a compositional layout while exploring the new APIs. Then, we’ll expand on our compositional layout solution while introducing some of the more powerful new features.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;iPhone simulator screenshot of a basic grid&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/961145da0bc7c167f0229201ec4d576860e6d364-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=331 331w, https://cdn.sanity.io/images/nkt6o869/production/961145da0bc7c167f0229201ec4d576860e6d364-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=663 663w, https://cdn.sanity.io/images/nkt6o869/production/961145da0bc7c167f0229201ec4d576860e6d364-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=994 994w, https://cdn.sanity.io/images/nkt6o869/production/961145da0bc7c167f0229201ec4d576860e6d364-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325 1325w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/961145da0bc7c167f0229201ec4d576860e6d364-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325&quot; width=&quot;1325&quot; height=&quot;2616&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;A basic grid built with UICollectionViewFlowLayout.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Our flow layout and compositional layout will use the exact same data source. It’s just a standard, everyday &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Data​Source&lt;/code&gt; that returns a cell for each square photo we want to display. The photos are categorized into sections, but that won’t come into play just yet.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;extension&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; PhotosDataSource&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;UICollectionViewDataSource &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // MARK: - UICollectionViewDataSource&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; numberOfSections&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; collectionView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: UICollectionView) -&gt; &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; sections.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;count&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; collectionView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; collectionView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: UICollectionView, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;numberOfItemsInSection&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) -&gt; &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; sections[section].&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;items&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;count&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; collectionView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; collectionView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: UICollectionView, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cellForItemAt&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; indexPath&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: IndexPath) -&gt; UICollectionViewCell {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        guard&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; cell = collectionView.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dequeueReusableCell&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;withReuseIdentifier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;PhotoCell&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: indexPath) as? PhotoCell &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;             return&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; UICollectionViewCell&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; photo = sections[indexPath.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;section&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;items&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[indexPath.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;item&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        cell.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;viewModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = PhotoCell.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ViewModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;identifier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: photo.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;identifier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;imageURL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: photo.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;thumbnailURL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; cell&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;We also need to register our cell under the reuse identifier used by our data source. In this example, our cell comes from a nib named &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Photo​Cell&lt;/code&gt;.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;collectionView.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;register&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UINib&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;nibName&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;PhotoCell&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bundle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forCellWithReuseIdentifier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;PhotoCell&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;The flow layout is fairly simple to set up. In the screenshot above, we can see 5 points of spacing between cells, both vertically and horizontally, as well as 5 point margins surrounding the layout.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; flowLayout: UICollectionViewFlowLayout = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; layout = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UICollectionViewFlowLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    layout.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;minimumInteritemSpacing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    layout.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;minimumLineSpacing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    layout.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;sectionInset&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UIEdgeInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;left&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bottom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;right&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; layout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Our cell layout is uniform. Every item is the same size, and we want exactly three items in every row. Since that size will change dynamically to fill the horizontal space on various screen sizes, we can’t simply set &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;item​Size&lt;/code&gt; once upfront. Instead, we’ll implement the following &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Delegate​Flow​Layout&lt;/code&gt; method to provide a size based on the width of the collection view’s current bounds.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;extension&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; PhotosCollectionViewController&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;UICollectionViewDelegateFlowLayout &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // MARK: - UICollectionViewDelegateFlowLayout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; collectionView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; collectionView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: UICollectionView, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layout&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; collectionViewLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: UICollectionViewLayout, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sizeForItemAt&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; indexPath&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: IndexPath) -&gt; CGSize {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; width = collectionView.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;bounds&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;width&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; numberOfItemsPerRow: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; spacing: CGFloat = flowLayout.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;minimumInteritemSpacing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; availableWidth = width - spacing * (numberOfItemsPerRow + &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; itemDimension = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;floor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(availableWidth / numberOfItemsPerRow)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; CGSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: itemDimension, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: itemDimension)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;In this implementation, we calculate &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;available​Width&lt;/code&gt; based on how much of our width will be taken up by margins and inter-item spacing (assuming we use the same value for both for simplicity) and subtracting it from the width of the collection view’s current bounds, then evenly dividing &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;available​Width&lt;/code&gt; by the number of items we want per row. If we ever wanted to change the number of items per row, we only need to change &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;number​Of​Items​Per​Row&lt;/code&gt; and the rest of the layout will work as is.&lt;/p&gt;&lt;p&gt;And that’s it! Well… sort of. There are a few more adjustments we’d have to make for our layout to be &lt;em&gt;perfect&lt;/em&gt;. Right now, if we rotate to and from landscape, our layout doesn’t properly invalidate, and we end up with a different number of items per row.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;iPhone simulator screenshot of a grid&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c68d55d183081043e1300e08fe83d6a4b04e20c1-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=331 331w, https://cdn.sanity.io/images/nkt6o869/production/c68d55d183081043e1300e08fe83d6a4b04e20c1-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=663 663w, https://cdn.sanity.io/images/nkt6o869/production/c68d55d183081043e1300e08fe83d6a4b04e20c1-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=994 994w, https://cdn.sanity.io/images/nkt6o869/production/c68d55d183081043e1300e08fe83d6a4b04e20c1-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325 1325w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c68d55d183081043e1300e08fe83d6a4b04e20c1-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325&quot; width=&quot;1325&quot; height=&quot;2616&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Our flow layout after rotating the device :(&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;We can fix this in a number of different ways. We could detect when our view’s &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;bounds&lt;/code&gt; change, and make the appropriate call to &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;invalidate​Layout()&lt;/code&gt;. We could subclass &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Flow​Layout&lt;/code&gt; and override &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;should​Invalidate​Layout(for​Bounds​Change:)&lt;/code&gt;. We could place some random combination of &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;invalidate​Layout()&lt;/code&gt; and &lt;code index=&quot;11&quot; isInline=&quot;true&quot;&gt;reload​Data()&lt;/code&gt; in various &lt;code index=&quot;13&quot; isInline=&quot;true&quot;&gt;UI​View​Controller&lt;/code&gt; methods, cross our fingers, and hope it doesn’t affect any animations we may want to add in the future 🤞 (we’ve all been there).&lt;/p&gt;&lt;p&gt;But wouldn’t it be nice to just describe our layout upfront and not deal with any of that? If only we knew how to use that shiny, new declarative API.&lt;/p&gt;&lt;h3&gt;Understanding &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Compositional​Layout&lt;/code&gt; by Example&lt;/h3&gt;&lt;p&gt;Forget everything you know about collection view layouts! Okay, well… not so fast. But we do need to think a bit differently about traditional section- and item-based layouts. In our flow layout, a section was comprised of multiple items, laid out in horizontal lines that were stacked vertically.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Diagram of a flow layout with items laid out in horizontal lines within a section&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f6c8fd4255f8e6d33318e119c33dde1f803a8c1c-944x944.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=236 236w, https://cdn.sanity.io/images/nkt6o869/production/f6c8fd4255f8e6d33318e119c33dde1f803a8c1c-944x944.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=472 472w, https://cdn.sanity.io/images/nkt6o869/production/f6c8fd4255f8e6d33318e119c33dde1f803a8c1c-944x944.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=708 708w, https://cdn.sanity.io/images/nkt6o869/production/f6c8fd4255f8e6d33318e119c33dde1f803a8c1c-944x944.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=944 944w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f6c8fd4255f8e6d33318e119c33dde1f803a8c1c-944x944.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=944&quot; width=&quot;944&quot; height=&quot;944&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Diagram of a flow layout with items laid out in horizontal lines within a section.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Compositional​Layout&lt;/code&gt; introduces groups, a powerful layer conceptually nestled somewhere between sections and items that we must understand to fully unlock their potential. For now, just know that a layout is comprised of sections, sections are comprised of groups, and groups are comprised of items and optionally other groups. We won’t nest groups within other groups just yet. Take a look at how our layout can be modified to incorporate groups.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Diagram of a compositional layout with items nested in groups within a section.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/174030a12f5f0f1620bbf4eb9c1fa81cfcd3a61c-939x939.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=235 235w, https://cdn.sanity.io/images/nkt6o869/production/174030a12f5f0f1620bbf4eb9c1fa81cfcd3a61c-939x939.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=470 470w, https://cdn.sanity.io/images/nkt6o869/production/174030a12f5f0f1620bbf4eb9c1fa81cfcd3a61c-939x939.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=704 704w, https://cdn.sanity.io/images/nkt6o869/production/174030a12f5f0f1620bbf4eb9c1fa81cfcd3a61c-939x939.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=939 939w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/174030a12f5f0f1620bbf4eb9c1fa81cfcd3a61c-939x939.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=939&quot; width=&quot;939&quot; height=&quot;939&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Diagram of a compositional layout with items nested in groups within a section.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Sections and items described in a compositional layout correspond 1-to-1 with the sections and items of our collection view data source. Groups, however, don’t have a data source equivalent, nor do they render content like items/cells. They’re used solely to describe the layout of our items within a section.&lt;/p&gt;&lt;p&gt;In the diagram above, each group represents a horizontal line of our layout. We’ll get fancier in a bit, but for now, we’ll structure our first compositional layout in this manner. Speaking of our first compositional layout, let’s get started on that now. We’ll want to create our layout using the initializer &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewcompositionallayout/3199211-init&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;init(section:)&lt;/code&gt;&lt;/a&gt;, which takes an &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutsection&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Section&lt;/code&gt;&lt;/a&gt; as a parameter. Note that &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Section&lt;/code&gt; simply &lt;em&gt;describes&lt;/em&gt; the layout of any section in the layout we’re creating. We’re not adding any data to the collection view here, as that’s the responsibility of the &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Data​Source&lt;/code&gt;.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; section = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSection&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;group&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: group)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; layout = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UICollectionViewCompositionalLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;section&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: section) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Oh, but we need to declare that &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;group&lt;/code&gt;. That’s right…because a layout is comprised of sections, which are comprised of groups, which are comprised of groups and items. Let’s work backwards from here.&lt;/p&gt;&lt;p&gt;A group is represented by a &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutgroup&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Group&lt;/code&gt;&lt;/a&gt;. To create one, we need to describe its size relative to its containing section, specify whether we want it to lay items out horizontally or vertically, and describe the items it will contain. We’ll use the class function &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutgroup/3213855-horizontal&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Group.horizontal(layout​Size:subitems:)&lt;/code&gt;&lt;/a&gt; to do this, since we want each group to lay out its items horizontally, but first we need to understand sizing.&lt;/p&gt;&lt;p&gt;Sizing is accomplished by describing groups and items in relation to their respective container’s size. More specifically, the size of a group can be described relative to its containing group or section’s size, and the size of an item can be described relative to its containing group’s size. To mimic our flow layout, we want to use the full section width to lay out three items in each group. To do so, we can easily describe the width of our group using &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutdimension&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Dimension&lt;/code&gt;&lt;/a&gt;. More specifically, we can create this using the &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutdimension/3199059-fractionalwidth&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Dimension.fractional​Width(_:)&lt;/code&gt;&lt;/a&gt; class method, passing &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;1&lt;/code&gt; to indicate that we want our group width to be 1 x the containing section’s width.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; widthDimension = NSCollectionLayoutDimension.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;We also need to provide a height dimension. Effectively, we want our groups to be as tall as our items, but we haven’t yet specified anything about item size just yet. Thinking ahead, we know we want to lay out 3 items horizontally in each group, and we know that our items are square (that is, their height equals their width). In our flow layout, we divided our available width by 3 to determine the item width and height, and we can do the same with &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;fractional​Width(_:)&lt;/code&gt;.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; heightDimension = NSCollectionLayoutDimension.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Remember, though, that we’re defining our &lt;em&gt;group&lt;/em&gt; size right now, not our item size. Currently, our groups just happen to be the same desired height as our items. Putting this all together, we create an &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutsize&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Size&lt;/code&gt;&lt;/a&gt; with our width and height dimensions, and use that to initialize our group. We then use our group to initialize our section, and our section to initialize our layout as we saw before.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; groupSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; group = NSCollectionLayoutGroup.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;horizontal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: groupSize, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subitems&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [item])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; section = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSection&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;group&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: group)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; layout = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UICollectionViewCompositionalLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;section&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: section) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;It’s extremely powerful that we’re able to describe height relative to the container’s width, and vice versa. Since our list scrolls vertically, we can’t really describe our group height in terms of the section’s height, as the section’s height is dependent on the number of items in our data source, and that has nothing to do with our group size. Sometimes, you may want to specify a specific, non-container-relative width or height. You can do so using &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutdimension/3199055-absolute&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Dimension.absolute(_:)&lt;/code&gt;&lt;/a&gt;. Similarly, for self-sizing groups and items, you can use &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutdimension/3199057-estimated&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Dimension.estimated(_:)&lt;/code&gt;&lt;/a&gt; and the actual dimension will be computed during layout.&lt;/p&gt;&lt;p&gt;We now need to define our layout’s items, which we used to create the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Group&lt;/code&gt; above. Items in the layout are represented by &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutitem&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Item&lt;/code&gt;&lt;/a&gt;, and just like groups, they are initialized with an &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Size&lt;/code&gt;. Now that we know how sizing works, this should be easy. Each item is as tall as its containing group (which we’ve already sized appropriately), and we want 3 items laid out horizontally in each group, so our item width will be a third of its containing group’s width.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; itemSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalHeight&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; item = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutItem&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: itemSize) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Our layout only uses a single &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Item&lt;/code&gt; because our cells are uniformly sized, but we could (and later &lt;em&gt;will&lt;/em&gt;) create a group with multiple items. For now, we put everything we just learned together to create a complete layout:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; compositionalLayout: UICollectionViewCompositionalLayout = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; fraction: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Item&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; itemSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(fraction), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalHeight&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; item = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutItem&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: itemSize)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Group&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; groupSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(fraction))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; group = NSCollectionLayoutGroup.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;horizontal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: groupSize, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subitems&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [item])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; section = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSection&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;group&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: group)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; UICollectionViewCompositionalLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;section&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: section)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;And that’s it. Our entire layout is declared up front. Our sizing logic from &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Delegate​Flow​Layout&lt;/code&gt; is no longer required, as that behavior is already captured in our layout declaration. Let’s take a look.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;iPhone simulator screenshot of a grid layout&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/30575714bb0b746e6ba9938245b120061378c552-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=331 331w, https://cdn.sanity.io/images/nkt6o869/production/30575714bb0b746e6ba9938245b120061378c552-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=663 663w, https://cdn.sanity.io/images/nkt6o869/production/30575714bb0b746e6ba9938245b120061378c552-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=994 994w, https://cdn.sanity.io/images/nkt6o869/production/30575714bb0b746e6ba9938245b120061378c552-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325 1325w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/30575714bb0b746e6ba9938245b120061378c552-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325&quot; width=&quot;1325&quot; height=&quot;2616&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Our first attempt at a compositional layout. We’re almost there…&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Ok, that’s not &lt;em&gt;quite&lt;/em&gt; it, but we’re close. We didn’t account for margins and item spacing like we did in our flow layout. Remember that in our flow layout, we needed to mathematically determine how much total horizontal spacing our layout required to determine our item size, and we also needed to specify &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;minimum​Interitem​Spacing&lt;/code&gt;, &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;minimum​Line​Spacing&lt;/code&gt;, and &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;section​Inset&lt;/code&gt; to get everything working perfectly. Achieving similar results in our compositional layout is quite simple by setting insets on &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;item&lt;/code&gt; and &lt;code index=&quot;11&quot; isInline=&quot;true&quot;&gt;section&lt;/code&gt;.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; inset: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2.5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// after item declaration…&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;item.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;contentInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSDirectionalEdgeInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;leading&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bottom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;trailing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// after section delcaration…&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;section.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;contentInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSDirectionalEdgeInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;leading&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bottom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;trailing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Note that we use &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;2.5&lt;/code&gt; instead of &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;5&lt;/code&gt; because each item gets this padding, so an item next to another item or a section edge will have a combined padding of &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;5&lt;/code&gt;. Also, we don’t have to calculate how much total horizontal space is required per row. Our &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;.fractional​Width(1/3)&lt;/code&gt; still works because our spacing is accomplished via content &lt;em&gt;insets&lt;/em&gt;, so our item width and height, including insets, are still one third of the width of our containing group. The content (cell) is simply inset within this size.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;iPhone simulator screenshot of a grid layout&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c031c6c0d739b14ab2b87c70d2d3c61afd3eaac3-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=331 331w, https://cdn.sanity.io/images/nkt6o869/production/c031c6c0d739b14ab2b87c70d2d3c61afd3eaac3-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=663 663w, https://cdn.sanity.io/images/nkt6o869/production/c031c6c0d739b14ab2b87c70d2d3c61afd3eaac3-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=994 994w, https://cdn.sanity.io/images/nkt6o869/production/c031c6c0d739b14ab2b87c70d2d3c61afd3eaac3-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325 1325w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c031c6c0d739b14ab2b87c70d2d3c61afd3eaac3-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325&quot; width=&quot;1325&quot; height=&quot;2616&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Our compositional layout now matches our flow layout!&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;✨ Now our layout is perfect. Not only does it properly maintain 3 columns when rotating our device back and forth, but the layout properly adjusts to respect the leading and trailing safe area margins without any additional code!&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;iPhone simulator rotated into landscape, showing a grid layout&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/45988b7973ba8ccdc3b8f62e3756ef0d9bd641ca-2617x1326.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/45988b7973ba8ccdc3b8f62e3756ef0d9bd641ca-2617x1326.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/45988b7973ba8ccdc3b8f62e3756ef0d9bd641ca-2617x1326.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/45988b7973ba8ccdc3b8f62e3756ef0d9bd641ca-2617x1326.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/45988b7973ba8ccdc3b8f62e3756ef0d9bd641ca-2617x1326.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/45988b7973ba8ccdc3b8f62e3756ef0d9bd641ca-2617x1326.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/45988b7973ba8ccdc3b8f62e3756ef0d9bd641ca-2617x1326.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2617 2617w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/45988b7973ba8ccdc3b8f62e3756ef0d9bd641ca-2617x1326.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;811&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt; The same compositional layout in landscape, inset by the safe area.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Using &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Compositional​Layout&lt;/code&gt;, we successfully matched the design of our flow layout using only a few lines of code, and we even fixed some bugs along the way. But that’s not all we can do with a compositional layout. We’re just getting started.&lt;/p&gt;&lt;h3&gt;Supplementary Items&lt;/h3&gt;&lt;p&gt;We’ve accomplished a basic layout of our cells, but if you’ve worked with collection views before, you might know that your data source is also capable of vending reusable supplementary views to create things like headers, accessory views, and the like. So how do those fit into compositional layouts? Before we get there, let’s add a method to our data source to create a view we’d like to use as a header for the sections in our collection view.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;extension&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; PhotosDataSource&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;UICollectionViewDataSource &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // MARK: - UICollectionViewDataSource&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // …&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; collectionView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; collectionView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: UICollectionView, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;viewForSupplementaryElementOfKind&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; kind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;at&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; indexPath&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: IndexPath) -&gt; UICollectionReusableView {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        guard&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; headerView = collectionView.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dequeueReusableSupplementaryView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ofKind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: kind, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;withReuseIdentifier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;HeaderSupplementaryView&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: indexPath) as? HeaderSupplementaryView &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; HeaderSupplementaryView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        headerView.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;viewModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = HeaderSupplementaryView.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ViewModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Section &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;indexPath.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;section&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; headerView&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Here, we’re simply returning a reusable instance of a basic header view class that we created and specifying its title as “Section” followed by the section number.&lt;/p&gt;&lt;p&gt;Just like we did with our &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Photo​Cell&lt;/code&gt;, we’ll need to register &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Header​Supplementary​View&lt;/code&gt; with our collection view.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;collectionView.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;register&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UINib&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;nibName&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;HeaderSupplementaryView&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bundle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forSupplementaryViewOfKind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;header&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;withReuseIdentifier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;HeaderSupplementaryView&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;And now we’re ready to incorporate this into our layout. For a flow layout, you’d either set &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewflowlayout/1617710-headerreferencesize&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;header​Reference​Size&lt;/code&gt;&lt;/a&gt; or implement &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewdelegateflowlayout/1617702-collectionview&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;collection​View(_:layout:reference​Size​For​Header​In​Section:)&lt;/code&gt;&lt;/a&gt; as part of your &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Delegate​Flow​Layout&lt;/code&gt; conformance. It’s just as simple to accomplish in a compositional layout.&lt;/p&gt;&lt;p&gt;First we need to create the layout-equivalent of our supplementary view, that is, the component that describes the header in our layout declaration. Look no further than &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutboundarysupplementaryitem&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Boundary​Supplementary​Item&lt;/code&gt;&lt;/a&gt;. We’ll simply create an instance and set it on our &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Section&lt;/code&gt;. Sizing works the same way as our items and groups, using &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Size&lt;/code&gt;. Then, we just need specify the element kind under which we registered, and &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;.top&lt;/code&gt; or &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;.bottom&lt;/code&gt; for the supplementary view’s alignment (note that we’d choose &lt;code index=&quot;11&quot; isInline=&quot;true&quot;&gt;.bottom&lt;/code&gt; if we wanted this to be a section footer).&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; headerItemSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;estimated&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; headerItem = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutBoundarySupplementaryItem&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: headerItemSize, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;elementKind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;header&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;alignment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;section.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;boundarySupplementaryItems&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [headerItem]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;With these three lines of code, our layout now supports headers!&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;iPhone simulator screenshot showing a compositional layout with supplementary header items&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6cd32f28338970b6a2b47ab1ce30f2b2eaa8f20f-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=331 331w, https://cdn.sanity.io/images/nkt6o869/production/6cd32f28338970b6a2b47ab1ce30f2b2eaa8f20f-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=663 663w, https://cdn.sanity.io/images/nkt6o869/production/6cd32f28338970b6a2b47ab1ce30f2b2eaa8f20f-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=994 994w, https://cdn.sanity.io/images/nkt6o869/production/6cd32f28338970b6a2b47ab1ce30f2b2eaa8f20f-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325 1325w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6cd32f28338970b6a2b47ab1ce30f2b2eaa8f20f-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325&quot; width=&quot;1325&quot; height=&quot;2616&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Compositional layout with supplementary header items.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;It’s just as easy to make these headers and footers “float” as you scroll by setting &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutboundarysupplementaryitem/3199044-pintovisiblebounds&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;pin​To​Visible​Bounds&lt;/code&gt;&lt;/a&gt; to &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;true&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;So what else can we display with supplementary items? Well… pretty much anything you want. &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Boundary​Supplementary​Item&lt;/code&gt; is actually a subclass of &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutsupplementaryitem&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Supplementary​Item&lt;/code&gt;&lt;/a&gt;. Using this class directly, we can easily control the sizing and positioning of supplementary views by attaching them to items and groups. A common example is displaying a badge on specific items, as demonstrated in &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutsupplementaryitem#overview&quot;&gt;the docs&lt;/a&gt;. Let’s use a similar approach and add a banner to the bottom of specific items. We’ll have the banner read “NEW” and it will extend beyond our item’s visible bounds.&lt;/p&gt;&lt;p&gt;First, just like we did for our section header items, we need to provide a reusable view from our data source. We’ll modify our data source method to check the element kind, and return a configured instance of the appropriate view.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; collectionView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; collectionView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: UICollectionView, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;viewForSupplementaryElementOfKind&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; kind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;at&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; indexPath&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: IndexPath) -&gt; UICollectionReusableView {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    switch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; kind {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;header&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        guard&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; headerView = collectionView.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dequeueReusableSupplementaryView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ofKind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: kind, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;withReuseIdentifier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;HeaderSupplementaryView&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: indexPath) as? HeaderSupplementaryView &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; HeaderSupplementaryView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        headerView.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;viewModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = HeaderSupplementaryView.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ViewModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Section &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;indexPath.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;section&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; headerView&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;new-banner&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; bannerView = collectionView.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dequeueReusableSupplementaryView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ofKind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: kind, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;withReuseIdentifier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;NewBannerSupplementaryView&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: indexPath)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        bannerView.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isHidden&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = indexPath.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;row&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; % &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; // show on every 5th item&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; bannerView&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    default&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        assertionFailure&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Unexpected element kind: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;\(&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;kind&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; UICollectionReusableView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Our banner is backed by a simple nib-based view, &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;New​Banner​Supplementary​View&lt;/code&gt;, that displays the “NEW” label and sets a background color and border. This data source method must return a non-optional &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;UI​Collection​Reusable​View&lt;/code&gt;, so even though we don’t want one on &lt;em&gt;every&lt;/em&gt; item, we must provide one. The docs state:&lt;/p&gt;&lt;blockquote&gt;If you do not want a supplementary view in a particular case, your layout object should not create the attributes for that view. Alternatively, you can hide views by setting the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;is​Hidden&lt;/code&gt; property of the corresponding attributes to &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;true&lt;/code&gt; or set the &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;alpha&lt;/code&gt; property of the attributes to &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;0&lt;/code&gt;.&lt;/blockquote&gt;&lt;p&gt;Since we’re not in the business of subclassing &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Layout&lt;/code&gt; anymore, we’re not controlling the creation of attributes, so we use the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;is​Hidden&lt;/code&gt; approach here. For demonstration purposes, we set our data source up to show this supplementary item on every 5th cell.&lt;/p&gt;&lt;p&gt;Again, we have to register our nib under the reuse identifier and element kind we just used.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;collectionView.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;register&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UINib&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;nibName&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;NewBannerSupplementaryView&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bundle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forSupplementaryViewOfKind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;new-banner&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;withReuseIdentifier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;NewBannerSupplementaryView&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;And that brings us to the layout updates. We’ll be creating an &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Supplementary​Item&lt;/code&gt; with the initializer &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutsupplementaryitem/3213899-init&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;init(layout​Size:element​Kind:container​Anchor:)&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;layout​Size&lt;/code&gt; is an &lt;code index=&quot;2&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Size&lt;/code&gt;, nothing new. But it’s worth mentioning that when using fractional width and height, the values are relative to the associated item or group you will be attaching the supplementary item to. We want our banner to be slightly wider than the item it’s attached to, so we’ll use a fractional width of &lt;code index=&quot;4&quot; isInline=&quot;true&quot;&gt;1.1&lt;/code&gt;. We can simply fix the height to a value that looks good, in this case &lt;code index=&quot;6&quot; isInline=&quot;true&quot;&gt;30&lt;/code&gt;, but could also use &lt;code index=&quot;8&quot; isInline=&quot;true&quot;&gt;.estimated&lt;/code&gt; just as easily and let Auto Layout do its thing.&lt;/li&gt;&lt;li&gt;Similar to our header, &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;element​Kind&lt;/code&gt; needs to match the string we used in our data source and nib registration. In this case, that’s &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;&amp;quot;new-banner&amp;quot;&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Lastly, we have &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;container​Anchor&lt;/code&gt;, which is of type &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutanchor&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Anchor&lt;/code&gt;&lt;/a&gt;. It’s initialized with a set of edges (&lt;a href=&quot;https://developer.apple.com/documentation/uikit/nsdirectionalrectedge&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Directional​Rect​Edge&lt;/code&gt;&lt;/a&gt;), and optionally a fixed or fractional offset from that edge. It determines where on our item or group to attach the supplementary item. We are going to attach it to the bottom, with a y-offset of &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;10&lt;/code&gt; so that it extends below our item as well.&lt;/li&gt;&lt;/ul&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;The “container” referred to in &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;container​Anchor&lt;/code&gt; is the item or group you’re attaching the supplementary item to. This terminology is a bit confusing, since you can optionally provide an &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutsupplementaryitem/3199113-itemanchor?changes=la_11&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;item​Anchor&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;However, “item” in &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;item​Anchor&lt;/code&gt; refers to the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Supplementary​Item&lt;/code&gt;, not the &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Item&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;So, while &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;container​Anchor&lt;/code&gt; determines where to anchor your supplementary view to an item or group in the layout, also specifying &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;item​Anchor&lt;/code&gt; determines exactly what part of the supplementary view is used as an anchor to attach to &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;container​Anchor&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Confusing!&lt;/p&gt;  &lt;/aside&gt;&lt;p&gt;We want a larger inset on all of our components so our banner has some additional room to extend beyond our items’ bounds, so we’ll replace &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;2.5&lt;/code&gt; with &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;8&lt;/code&gt;. The following shows the supplementary item and item declaration. Note that the item declaration now makes use of &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;supplementary​Item&lt;/code&gt;. The group and section declarations remain the same as before, but without the header supplementary items.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; inset: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Supplementary Item&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; layoutSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1.1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;absolute&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;30&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; containerAnchor = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutAnchor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;edges&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;bottom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;], &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;absoluteOffset&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CGPoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;y&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; supplementaryItem = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSupplementaryItem&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: layoutSize, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;elementKind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;new-banner&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;containerAnchor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: containerAnchor)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Item&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; itemSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(fraction), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalHeight&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; item = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutItem&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: itemSize, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;supplementaryItems&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [supplementaryItem])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;item.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;contentInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSDirectionalEdgeInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;leading&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bottom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;trailing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;And the results?&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;iPhone simulator screenshot showing a compositional layout with item-based supplementary items&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/32459616a14727d59c57254967b97aecbb9d4c05-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=331 331w, https://cdn.sanity.io/images/nkt6o869/production/32459616a14727d59c57254967b97aecbb9d4c05-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=663 663w, https://cdn.sanity.io/images/nkt6o869/production/32459616a14727d59c57254967b97aecbb9d4c05-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=994 994w, https://cdn.sanity.io/images/nkt6o869/production/32459616a14727d59c57254967b97aecbb9d4c05-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325 1325w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/32459616a14727d59c57254967b97aecbb9d4c05-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325&quot; width=&quot;1325&quot; height=&quot;2616&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Compositional layout with item-based supplementary items.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;We’ve now seen how to add various kinds of supplementary items to our compositional layout, but the customization doesn’t stop there.&lt;/p&gt;&lt;h3&gt;Decoration Items&lt;/h3&gt;&lt;p&gt;In addition to supplementary items, we can customize our section layout with decoration items. This will allow us to easily add backgrounds to our sections. The background view we’ll create is quite simple (a gray rectangle with a corner radius), so we’ll do it in code.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// A basic supplementary view used for section backgrounds.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;final&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; BackgroundSupplementaryView&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;UICollectionReusableView &lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    override&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; init&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;frame&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: CGRect) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        super&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;frame&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: frame)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        layer.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cornerRadius&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        backgroundColor = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UIColor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;white&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.85&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;alpha&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Perhaps surprisingly, we don’t need to update our data source or register this view on the collection view. We’ll handle registration directly on the layout. And speaking of the layout, in order to accommodate the background view, we’ll want to inset our section a bit more to display more of the new background, so instead of using our uniform &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;2.5&lt;/code&gt; for all of our spacing like we were before, let’s specify a new value for our section &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;content​Insets&lt;/code&gt;.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; sectionInset: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;section.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;contentInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSDirectionalEdgeInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: sectionInset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;leading&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: sectionInset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bottom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: sectionInset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;trailing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: sectionInset) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;In our layout declaration, we simply create a new decoration item for our background using &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutdecorationitem/3199051-background&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Decoration​Item.background(element​Kind:)&lt;/code&gt;&lt;/a&gt;, inset it a bit, and set it on our section.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; backgroundItem = NSCollectionLayoutDecorationItem.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;background&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;elementKind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;background&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; backgroundInset: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;backgroundItem.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;contentInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSDirectionalEdgeInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: backgroundInset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;leading&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: backgroundInset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bottom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: backgroundInset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;trailing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: backgroundInset)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;section.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;decorationItems&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [backgroundItem]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;We then register it on the layout itself after we create it.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; layout = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UICollectionViewCompositionalLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;section&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: section)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;layout.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;register&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(BackgroundSupplementaryView.&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forDecorationViewOfKind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;background&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Now our background decoration view is ready.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;iPhone simulator screenshot showing a compositional layout with background decoration items&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c969d6fd44677dad71f4d6531a6023ad8d609fb1-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=331 331w, https://cdn.sanity.io/images/nkt6o869/production/c969d6fd44677dad71f4d6531a6023ad8d609fb1-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=663 663w, https://cdn.sanity.io/images/nkt6o869/production/c969d6fd44677dad71f4d6531a6023ad8d609fb1-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=994 994w, https://cdn.sanity.io/images/nkt6o869/production/c969d6fd44677dad71f4d6531a6023ad8d609fb1-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325 1325w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c969d6fd44677dad71f4d6531a6023ad8d609fb1-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325&quot; width=&quot;1325&quot; height=&quot;2616&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Compositional layout with background decoration items.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;And that’s all there is to it!&lt;/p&gt;&lt;h3&gt;Section Provider&lt;/h3&gt;&lt;p&gt;We’ve now successfully customized our layout using supplementary and decoration views, which opens a world of possibilities. But so far, our sections have been uniform. After all, we describe each section the same way, with a single &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Section&lt;/code&gt;, which is fairly limiting. Thus far, we’ve only used &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewcompositionallayout/3199211-init&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;init(section:)&lt;/code&gt;&lt;/a&gt; to create our &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Compositional​Layout&lt;/code&gt;, so we need to explore another API.&lt;/p&gt;&lt;p&gt;To provide a different layout on a section-by-section basis of a compositional layout, we need to create a &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewcompositionallayoutsectionprovider&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Compositional​Layout​Section​Provider&lt;/code&gt;&lt;/a&gt;. A section provider is simply a closure that is passed a section index and environment information, and returns an &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Section&lt;/code&gt;. Instead of &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;init(section:)&lt;/code&gt; we need to use &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Compositional​Layout&lt;/code&gt;’s &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewcompositionallayout/3199214-init&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;init(section​Provider:)&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;First, we’ll very lightly refactor our initial layout declaration with headers to use this initializer. We simply put all of the item, group, and section setup into the section provider closure.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; compositionalLayout = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UICollectionViewCompositionalLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sectionProvider&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: { (sectionIndex, environment) -&gt; NSCollectionLayoutSection? &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; fraction: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; inset: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2.5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Item&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; itemSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(fraction), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalHeight&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; item = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutItem&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: itemSize)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    item.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;contentInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSDirectionalEdgeInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;leading&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bottom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;trailing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Group&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; groupSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(fraction))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; group = NSCollectionLayoutGroup.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;horizontal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: groupSize, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subitems&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [item])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; section = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSection&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;group&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: group)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    section.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;contentInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSDirectionalEdgeInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;leading&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bottom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;trailing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Supplementary Item&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; headerItemSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;estimated&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; headerItem = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutBoundarySupplementaryItem&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: headerItemSize, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;elementKind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;header&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;alignment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    section.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;boundarySupplementaryItems&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [headerItem]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Things will look identical to how they did before, but now we’re ready to customize each section based on the section index and environment. To illustrate this, we’ll make a simple change to incorporate the section index. We’ll simply add the section index to the current number of items in each row (3). So for example, the first section (index 0) will have 3 items in each row, the second section will have 4 items in each row, the third section will have 5 items in each row, etc. We only need to change the declaration of &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;fraction&lt;/code&gt;.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; itemsPerRow = sectionIndex + &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; fraction: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CGFloat&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(itemsPerRow)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;And let’s see how it looks.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Two iPhone simulators side by side showing compositional layout with an increasing number of items per row in each section&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/596cbe0226d9b91bbf0f99bf27cce914c5ff2f06-2710x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/596cbe0226d9b91bbf0f99bf27cce914c5ff2f06-2710x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/596cbe0226d9b91bbf0f99bf27cce914c5ff2f06-2710x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/596cbe0226d9b91bbf0f99bf27cce914c5ff2f06-2710x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/596cbe0226d9b91bbf0f99bf27cce914c5ff2f06-2710x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/596cbe0226d9b91bbf0f99bf27cce914c5ff2f06-2710x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/596cbe0226d9b91bbf0f99bf27cce914c5ff2f06-2710x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2710 2710w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/596cbe0226d9b91bbf0f99bf27cce914c5ff2f06-2710x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1545&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Compositional layout with an increasing number of items per row in each section.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Note that we’re using the same items and groups in each section, just with slightly tweaked metrics, but you could very well use entirely different item and group layouts on a section-by-section basis if your design calls for it.&lt;/p&gt;&lt;p&gt;We can also use the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;environment&lt;/code&gt; parameter of the section provider to customize our layout. The environment is an &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutenvironment&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Environment&lt;/code&gt;&lt;/a&gt;, and provides us more context about the container that we can use to make decisions about how we want to layout our items, groups, and sections. The &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutenvironment/3199073-container&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;container&lt;/code&gt;&lt;/a&gt; property on the environment gives us information about the content size and insets of the layout. We also have access to &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutenvironment/3199074-traitcollection&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;trait​Collection&lt;/code&gt;&lt;/a&gt;, which allows us to easily make use of the layout’s size class and the screen’s scale factor. We’ll use the trait collection’s &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uitraitcollection/1623508-horizontalsizeclass&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;horizontal​Size​Class&lt;/code&gt;&lt;/a&gt; to determine the number of items to display in each row. For horizontally-regular environments, like full-screen iPad layouts, we’ll display double the number of items per row. For horizontally-compact environments, we’ll keep it at 3. All we need to do is change our &lt;code index=&quot;11&quot; isInline=&quot;true&quot;&gt;items​Per​Row&lt;/code&gt;.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; itemsPerRow = environment.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;traitCollection&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;horizontalSizeClass&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; == .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;compact&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ? &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; fraction: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CGFloat&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(itemsPerRow)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;And just like that, our layout scales nicely for different environments!&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Two iPad simulators side by side showing compositional layouts in different horizontal size classes&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9052d300b28522b5e4ea0bbb7a7a2b5568f7d6f5-4636x2973.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/9052d300b28522b5e4ea0bbb7a7a2b5568f7d6f5-4636x2973.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/9052d300b28522b5e4ea0bbb7a7a2b5568f7d6f5-4636x2973.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/9052d300b28522b5e4ea0bbb7a7a2b5568f7d6f5-4636x2973.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/9052d300b28522b5e4ea0bbb7a7a2b5568f7d6f5-4636x2973.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/9052d300b28522b5e4ea0bbb7a7a2b5568f7d6f5-4636x2973.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/9052d300b28522b5e4ea0bbb7a7a2b5568f7d6f5-4636x2973.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/9052d300b28522b5e4ea0bbb7a7a2b5568f7d6f5-4636x2973.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9052d300b28522b5e4ea0bbb7a7a2b5568f7d6f5-4636x2973.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1026&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Compositional layouts in different horizontal size classes.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;Nested Groups&lt;/h3&gt;&lt;p&gt;Once you’ve gotten used to building layouts using &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Compositional​Layout&lt;/code&gt;, you’ll likely find it much easier to accomplish the things you used to do with &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Flow​Layout&lt;/code&gt;, often with much less code. So far we’ve played with a few bells and whistles that flow layouts don’t support right out of the box. It’s now time to break away from a uniform, flow-like layout and build something that would’ve previously required subclassing &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Layout&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;When creating &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Group&lt;/code&gt;s, we’ve previously only supplied one item to the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;subitems&lt;/code&gt; parameter. This is because all of our items within our horizontal groups were uniform in size and insets. However, our layouts can consist of any number of unique &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Item&lt;/code&gt;s we’d like. But as we previously alluded to, we can also nest groups within our groups. If you haven’t figured it out already, groups are just a special kind of item that allow the nesting of other items. In fact, &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Group&lt;/code&gt; is a subclass of &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Item&lt;/code&gt;, so it’s not just a coincidence that we can modify size, spacing, and insets the same way.&lt;/p&gt;&lt;p&gt;Let’s try to build a more complicated layout using multiple items and nested groups. It’ll look like the following.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Diagram of a compositional layout with multiple items and nested groups.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a572324a9c557888d4badd2a8899f69596c98ef4-1270x941.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=318 318w, https://cdn.sanity.io/images/nkt6o869/production/a572324a9c557888d4badd2a8899f69596c98ef4-1270x941.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=635 635w, https://cdn.sanity.io/images/nkt6o869/production/a572324a9c557888d4badd2a8899f69596c98ef4-1270x941.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=953 953w, https://cdn.sanity.io/images/nkt6o869/production/a572324a9c557888d4badd2a8899f69596c98ef4-1270x941.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1270 1270w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a572324a9c557888d4badd2a8899f69596c98ef4-1270x941.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1270&quot; width=&quot;1270&quot; height=&quot;941&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p data-astro-cid-c6ccksbc&gt;Diagram of a compositional layout with multiple items and nested groups.&lt;/p&gt; &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;At this point, we know all of the major parts that compose a compositional layout, so we’ll work forward instead of backwards, starting with the items. In our diagram, items fall into two categories in terms of sizing relative to their containing group:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;The larger items take up half the width of their containing group, and the full height.&lt;/li&gt;&lt;li&gt;The smaller items take up the full width of their containing group, and the full width.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;We already know how to use &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Size&lt;/code&gt; to specify fractional widths and height, so let’s define these items.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; largeItemSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalHeight&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; largeItem = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutItem&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: largeItemSize)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; smallItemSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalHeight&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; smallItem = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutItem&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: smallItemSize)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Now we move onto the groups. Let’s start with the nested group first. Unlike the previous groups we’ve used, the nested groups lay items out vertically. Well, good news! &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutgroup/3213860-vertical&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Group.vertical(layout​Size:subitems:)&lt;/code&gt;&lt;/a&gt; works exactly like its horizontal counterpart. Since we’ve already specified that we want the large item to take up half the width of the containing group, we have half of the width left for our nested groups. We want two of them, side-by-side, so we’ll make each of them take up a quarter of the containing group’s width, but use the full height. This vertical group only contains one type of item, the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;small​Item&lt;/code&gt; that we defined above.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; verticalGroupSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.25&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalHeight&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; verticalGroup = NSCollectionLayoutGroup.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;vertical&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: verticalGroupSize, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subitems&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [smallItem])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Finally, we need to declare our outer group. This group contains a large item next to two nested groups, laid out horizontally. The group itself takes up the full width of its containing section, but its height is a bit trickier. Again, we know the large item takes up half the width of its containing group (which &lt;em&gt;is&lt;/em&gt; the outer group). Since it’s a square, we want its height to match. Much like we did with our first compositional layout, we’ll define this group’s height relative to its container’s width. More specifically, its height is equal to &lt;em&gt;half&lt;/em&gt; of its width. So… not &lt;em&gt;that&lt;/em&gt; tricky.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; outerGroupSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; outerGroup = NSCollectionLayoutGroup.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;horizontal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: outerGroupSize, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subitems&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [largeItem, nestedGroup, nestedGroup])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Finally, we’ll use this outer group to create our section, and create the the compositional layout from our section:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; section = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSection&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;group&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: outerGroup)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; UICollectionViewCompositionalLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;section&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: section)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;So how’s it look?&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;iPhone simulator showing compositional layout with multiple items and nested groups&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/0bd84a61a729458430751dcb5973c55b663e540c-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=331 331w, https://cdn.sanity.io/images/nkt6o869/production/0bd84a61a729458430751dcb5973c55b663e540c-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=663 663w, https://cdn.sanity.io/images/nkt6o869/production/0bd84a61a729458430751dcb5973c55b663e540c-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=994 994w, https://cdn.sanity.io/images/nkt6o869/production/0bd84a61a729458430751dcb5973c55b663e540c-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325 1325w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/0bd84a61a729458430751dcb5973c55b663e540c-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325&quot; width=&quot;1325&quot; height=&quot;2616&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Compositional layout with multiple items and nested groups.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Hey, not bad! Everything is exactly where we expect it to be.&lt;/p&gt;&lt;p&gt;You may have noticed that we specified three items in &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;subitems&lt;/code&gt; for our &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;outer​Group&lt;/code&gt; declaration:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;NSCollectionLayoutGroup.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;horizontal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: outerGroupSize, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subitems&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [largeItem, nestedGroup, nestedGroup])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;This &lt;em&gt;does&lt;/em&gt; match what we see on screen, but we didn’t have to repeat items in our &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;subitems&lt;/code&gt; array in our first compositional layout that had three items laid out horizontally, so what gives?&lt;/p&gt;&lt;p&gt;When using &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Group.horizontal(layout​Size:subitem:)&lt;/code&gt; and &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Group.vertical(layout​Size:subitem:)&lt;/code&gt;, the &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;subitems&lt;/code&gt; that you provide will repeat, in order, in the respective direction until the next subitem wouldn’t fit. So if our group specified only &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;[large​Item, nested​Group]&lt;/code&gt; for its &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;subitems&lt;/code&gt;, the next horizontal item after a large item and a vertical nested group would be the large item again. And since we’ve already eaten up 75% of the horizontal space in the group, we wouldn’t be able to fit another 50%-width item. So it’s necessary for us to specify all three items/groups in this order, otherwise we’d end up with this:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;iPhone simulator screenshot showing compositional layout next to empty space&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/0606e097cbd6d2dff753fbd3cc71a693b768944d-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=331 331w, https://cdn.sanity.io/images/nkt6o869/production/0606e097cbd6d2dff753fbd3cc71a693b768944d-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=663 663w, https://cdn.sanity.io/images/nkt6o869/production/0606e097cbd6d2dff753fbd3cc71a693b768944d-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=994 994w, https://cdn.sanity.io/images/nkt6o869/production/0606e097cbd6d2dff753fbd3cc71a693b768944d-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325 1325w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/0606e097cbd6d2dff753fbd3cc71a693b768944d-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325&quot; width=&quot;1325&quot; height=&quot;2616&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Compositional layout with only two items specified per outer group: &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;large​Item&lt;/code&gt; and &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;nested​Group&lt;/code&gt;. Empty horizontal space fills the rest of each outer group.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Before we call this layout complete, let’s spruce it up with the same insets and headers that we used before. The completed layout is as follows:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; compositionalLayout: UICollectionViewCompositionalLayout = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; inset: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2.5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Items&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; largeItemSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalHeight&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; largeItem = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutItem&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: largeItemSize)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    largeItem.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;contentInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSDirectionalEdgeInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;leading&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bottom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;trailing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; smallItemSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalHeight&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; smallItem = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutItem&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: smallItemSize)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    smallItem.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;contentInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSDirectionalEdgeInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;leading&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bottom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;trailing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Nested Group&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; nestedGroupSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.25&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalHeight&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; nestedGroup = NSCollectionLayoutGroup.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;vertical&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: nestedGroupSize, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subitems&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [smallItem])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Outer Group&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; outerGroupSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; outerGroup = NSCollectionLayoutGroup.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;horizontal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: outerGroupSize, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subitems&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [largeItem, nestedGroup, nestedGroup])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; section = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSection&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;group&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: outerGroup)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    section.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;contentInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSDirectionalEdgeInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;leading&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bottom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;trailing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: inset)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Supplementary Item&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; headerItemSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;estimated&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; headerItem = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutBoundarySupplementaryItem&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: headerItemSize, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;elementKind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;header&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;alignment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    section.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;boundarySupplementaryItems&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [headerItem]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;     &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; UICollectionViewCompositionalLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;section&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: section)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;And the result?&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;iPhone simulator showing compositional layout with nested groups with insets and section headers applied&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/bcaaf587b96c11fadc2decd1eb02f9051913c271-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=331 331w, https://cdn.sanity.io/images/nkt6o869/production/bcaaf587b96c11fadc2decd1eb02f9051913c271-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=663 663w, https://cdn.sanity.io/images/nkt6o869/production/bcaaf587b96c11fadc2decd1eb02f9051913c271-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=994 994w, https://cdn.sanity.io/images/nkt6o869/production/bcaaf587b96c11fadc2decd1eb02f9051913c271-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325 1325w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/bcaaf587b96c11fadc2decd1eb02f9051913c271-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325&quot; width=&quot;1325&quot; height=&quot;2616&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Compositional layout with nested groups with insets and section headers applied.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;*chef’s kiss*&lt;/p&gt;&lt;h3&gt;Orthogonal Scrolling&lt;/h3&gt;&lt;p&gt;Now for the main event. The most amazing feature of compositional layouts.&lt;/p&gt;&lt;p&gt;Have you ever had to build a vertically scrolling table view or collection view where some of the rows also scroll horizontally? You know, a gallery of sorts. Take the App Store for example:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A gif showing horizontal scrolling in the App Store&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f882544e73f05e9bf2dddf2cb5fea43dcc3e2a4d-392x846.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=196 196w, https://cdn.sanity.io/images/nkt6o869/production/f882544e73f05e9bf2dddf2cb5fea43dcc3e2a4d-392x846.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=392 392w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f882544e73f05e9bf2dddf2cb5fea43dcc3e2a4d-392x846.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=392&quot; width=&quot;392&quot; height=&quot;846&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;App Store: A gallery-style horizontally scrolling list within a vertically scrolling list.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;We refer to this behavior as “orthogonal scrolling”. That is, part of our layout scrolls along the opposite axis of the scrolling container. In this example, part of the layout scrolls horizontally in a vertically scrolling container.&lt;/p&gt;&lt;p&gt;How would you build this? I can’t tell you how many times I’ve nested horizontally scrolling flow-layout collection views inside of vertically scrolling table views. Scroll performance and view hierarchy architecture be damned!&lt;/p&gt;&lt;p&gt;What if I told you that you can achieve this behavior, allowing sections of your collection view to scroll horizontally within a vertically scrolling list without incurring a massive performance hit. What If I told you that you wouldn’t need to embed collection views inside of other cells? What if I told you that you could achieve this with &lt;strong&gt;One&lt;/strong&gt;. &lt;strong&gt;Line&lt;/strong&gt;. &lt;strong&gt;Of&lt;/strong&gt;. &lt;strong&gt;Code&lt;/strong&gt;.? Okay, okay. I’m exaggerating. OR AM I?&lt;/p&gt;&lt;p&gt;In one 👏 line 👏 of 👏 code 👏, we can set the &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutsection/3199094-orthogonalscrollingbehavior&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;orthogonal​Scrolling​Behavior&lt;/code&gt;&lt;/a&gt; on a section to achieve exactly what we want.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;section.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;orthogonalScrollingBehavior&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;groupPaging&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;That’s it. Applying this to the layout we created in the last section produces the following results:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A gif showing compositional layout with orthogonal scrolling behavior applied&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/cc454ba85d41e4800ad416484f1555ce219496a3-392x846.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=196 196w, https://cdn.sanity.io/images/nkt6o869/production/cc454ba85d41e4800ad416484f1555ce219496a3-392x846.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=392 392w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/cc454ba85d41e4800ad416484f1555ce219496a3-392x846.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=392&quot; width=&quot;392&quot; height=&quot;846&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Compositional layout with orthogonal scrolling behavior applied.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;🤯&lt;/p&gt;&lt;p&gt;We used the &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionlayoutsectionorthogonalscrollingbehavior/grouppaging&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;group​Paging&lt;/code&gt;&lt;/a&gt; behavior, which allows for swiping to scroll to, and immediately rest on, the next or previous group in the section. If we wanted to scroll continuously without paging, we could use &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionlayoutsectionorthogonalscrollingbehavior/continuous&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;continuous&lt;/code&gt;&lt;/a&gt;. There are &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionlayoutsectionorthogonalscrollingbehavior&quot;&gt;several more options to choose&lt;/a&gt; from, and chances are, you’ll be able to achieve the exact behavior you’re looking for, with &lt;strong&gt;One&lt;/strong&gt;. &lt;strong&gt;Line&lt;/strong&gt;. &lt;strong&gt;Of&lt;/strong&gt;. Okay, you get it.&lt;/p&gt;&lt;h3&gt;Bonus Round&lt;/h3&gt;&lt;p&gt;We’ve kissed &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Flow​Layout&lt;/code&gt; goodbye. We’ve built layouts that previously would’ve required subclassing &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Layout&lt;/code&gt;. We’re going to take a look at one final API that doesn’t get quite as much attention, but is definitely capable of unlocking more customization that might keep you from reaching for that old &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Layout&lt;/code&gt; subclassing approach.&lt;/p&gt;&lt;p&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Section&lt;/code&gt; has a &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutsection/3199096-visibleitemsinvalidationhandler&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;visible​Items​Invalidation​Handler&lt;/code&gt;&lt;/a&gt; property, documented as follows:&lt;/p&gt;&lt;blockquote&gt;A closure called before each layout cycle to allow modification of the items in the section immediately before they are displayed.&lt;/blockquote&gt;&lt;p&gt;Well, how could we use that? However you want, really. Play with it! Experiment! Animate your items!&lt;/p&gt;&lt;p&gt;Previously, I was no stranger to subclassing &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Layout&lt;/code&gt; and overriding &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewlayout/1617769-layoutattributesforelements&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;layout​Attributes​For​Element(in:)&lt;/code&gt;&lt;/a&gt; to do fun, fancy things, like scale cells so that they’re larger as they approach the center of the screen. No more.&lt;/p&gt;&lt;p&gt;Let’s start with a slightly modified version of our first layout again. We’ll ditch the item insets, add some additional spacing at the top of each section, and use &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;continuousorthogonal​Scrolling​Behavior&lt;/code&gt;.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; compositionalLayout: UICollectionViewCompositionalLayout = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; fraction: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1.0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;3.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Item&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; itemSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalHeight&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; item = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutItem&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: itemSize)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Group&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; groupSize = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;widthDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(fraction), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;heightDimension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;fractionalWidth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(fraction))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; group = NSCollectionLayoutGroup.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;horizontal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;layoutSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: groupSize, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subitems&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [item])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; section = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSCollectionLayoutSection&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;group&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: group)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    section.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;contentInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NSDirectionalEdgeInsets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;leading&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2.5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bottom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;trailing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2.5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    section.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;orthogonalScrollingBehavior&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;continuous&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; UICollectionViewCompositionalLayout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;section&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: section)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Now, let’s play around with &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;visible​Items​Invalidation​Handler&lt;/code&gt;. This closure passes an array of the visible items, represented by &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nscollectionlayoutvisibleitem&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Visible​Item&lt;/code&gt;&lt;/a&gt;, the current &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;scroll​Offset&lt;/code&gt;, and the layout environment, like we saw earlier in our section provider. We’ll use this information to calculate the horizontal distance of each visible item from the center of the container. Then we’ll scale each visible item, so that when they’re in the center, they’re larger than their normal size, and when they’re closer to the edges of the screen, they’re smaller.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;section.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;visibleItemsInvalidationHandler&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = { (items, offset, environment) &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    items.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { item &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; distanceFromCenter = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;abs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((item.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;frame&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;midX&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; - offset.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) - environment.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;container&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;contentSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2.0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; minScale: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; maxScale: CGFloat = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; scale = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;max&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(maxScale - (distanceFromCenter / environment.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;container&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;contentSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), minScale)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        item.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;transform&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CGAffineTransform&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;scaleX&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: scale, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;y&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: scale)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;So with just a little bit of math, we achieve… whatever this is!&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A gif showing horizontal scrolling through a grid&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e89ea62d455fba980c1122d0d713b00644b56a7e-392x846.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=196 196w, https://cdn.sanity.io/images/nkt6o869/production/e89ea62d455fba980c1122d0d713b00644b56a7e-392x846.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=392 392w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e89ea62d455fba980c1122d0d713b00644b56a7e-392x846.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=392&quot; width=&quot;392&quot; height=&quot;846&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;There’s a lot of power packed into this API, and like the other APIs we’ve explored, it can help to eliminate your need to write your own &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Layout&lt;/code&gt; subclass.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;We’ve come a long way since &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Collection​View&lt;/code&gt;’s introduction in 2012. Apple and third-party developers have continued to push the envelope with increasingly complex layouts in their apps that look and work beautifully. Emulating these complex layouts over the years proved to be frustrating, and not for the feint of heart. It takes a lot of hard work to assess these complications, and completely rethink the tools we use to build our user interfaces. While it might not be quite as fundamentally impactful as &lt;a href=&quot;https://developer.apple.com/xcode/swiftui/&quot;&gt;Swift UI&lt;/a&gt; in the longterm, &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Compositional​Layout&lt;/code&gt; is an amazing, feature packed API that addresses the needs of modern app developers.&lt;/p&gt;&lt;p&gt;Please consider this my love letter to Apple. I’m just… so impressed. 💌&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Interested in building an iOS app? We can help! &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Get in touch&lt;/a&gt;.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Michael Liberatore</author></item><item><title>Understanding Creational Design Patterns</title><link>https://lickability.com/blog/understanding-creational-design-patterns/</link><guid isPermaLink="true">https://lickability.com/blog/understanding-creational-design-patterns/</guid><description>The last part in our series</description><pubDate>Tue, 26 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Welcome to the third and final part of our series on understanding &lt;strong&gt;design patterns&lt;/strong&gt; in iOS development. In previous blog posts, we explored &lt;a href=&quot;https://lickability.com/blog/structural-design-patterns/&quot;&gt;structural&lt;/a&gt; and &lt;a href=&quot;https://lickability.com/blog/understanding-behavioral-design-patterns/&quot;&gt;behavioral&lt;/a&gt; design patterns. In this post, we‘ll take a look at a few examples of creational patterns, which allow us to create interfaces in our applications.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Builder_pattern&quot;&gt;🔨 The Builder Pattern&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;This pattern is composed of three types: the &lt;strong&gt;director&lt;/strong&gt;, the &lt;strong&gt;product&lt;/strong&gt;, and the &lt;strong&gt;builder&lt;/strong&gt;. The director takes the inputs and coordinates with the builder. The product is the complex object we are trying to create. And the builder accepts step-by-step instructions and handles the creation of the product.&lt;/p&gt;&lt;p&gt;To learn more about the builder pattern, I recommend reading &lt;a href=&quot;https://www.swiftbysundell.com/articles/using-the-builder-pattern-in-swift/&quot;&gt;John Sundell‘s explanation&lt;/a&gt;. He covers the core ideas behind the pattern, pros and cons of using it, and a few examples we can look at.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Factory_method_pattern&quot;&gt;🏭 The Factory Pattern&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;This pattern allows us to create objects without exposing how they are made. We can use this method when we are not sure what exact types and dependencies the code should work with.&lt;/p&gt;&lt;p&gt;We can also use this method if we want to give users of our framework a way to extend our internal components. This method is beneficial because it allows us to avoid tight coupling between the creator of the object and the concrete products. It follows the single responsibility principle by moving the object creation code to one place, ensuring that our object only has one responsibility. It also adheres to the open/closed principle, because it allows us to introduce new types into our code without breaking existing code.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Prototype_pattern&quot;&gt;📏 The Prototype Pattern&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;This a creational pattern that allows an object to clone itself. It‘s especially useful when creating a new instance of an object is expensive. Further, this method is useful because it allows all the properties and methods to be carried over when cloned, which is useful when subclassing is not practical.&lt;/p&gt;&lt;p&gt;You can give default values to the type which will be cloned and set these values after cloning. The prototype pattern is beneficial because it allows you to clone objects without coupling to their concrete classes, and complex objects may be created more conveniently.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Singleton_pattern&quot;&gt;1️⃣ The Singleton Pattern&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Before exploring the world of singletons, I thought they were the greatest thing ever created. By creating a singleton, we ensure that a class has only one instance, allowing us to control access to some shared resource. It also provides a global access point to that instance.&lt;/p&gt;&lt;p&gt;Some benefits to this pattern are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The singleton instance is created only when it is requested for the first time.&lt;/li&gt;&lt;li&gt;You gain access to the instance globally.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;However, some drawbacks to this pattern are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;It is not very thread-safe.&lt;/li&gt;&lt;li&gt;It can be difficult to test.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In the snippet above, we create a class with a private initializer—private, because we only want there to be one instance of this class. We created the property &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;shared&lt;/code&gt; which will be the only point of access to the instance of this class.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Dependency_injection&quot;&gt;🩹 Dependency Injection&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;An alternative to the Singleton pattern would be dependency injection. As the name suggests, we are injecting our objects with the dependencies they need to function.&lt;/p&gt;&lt;p&gt;Injecting your objects with its dependencies has a major advantage because it makes our objects fully testable. The easiest example of this comes from mocking/stubbing our networking layer.&lt;/p&gt;&lt;h4&gt;Constructor/Initializer Injection&lt;/h4&gt;&lt;p&gt;In this method of injection, we pass the dependency on initialization of the class. We also will need to create a read-only property inside of the class which will store a reference to the initialized property.&lt;/p&gt;&lt;h4&gt;Property Injection&lt;/h4&gt;&lt;p&gt;We can use this property in cases where we do not have access to the object‘s initialization but we do have access to how the desired property is initialized.&lt;/p&gt;&lt;p&gt;In the below snippet of the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;ice​Cream​Detailed​View​Controller&lt;/code&gt; we have a property &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;name&lt;/code&gt; which is set when the &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;main​View​Controller&lt;/code&gt; segues here.&lt;/p&gt;&lt;h4&gt;Method Injection&lt;/h4&gt;&lt;p&gt;Consider this method if you only want to use this property once. You can pass the dependency as a parameter of a method.&lt;/p&gt;&lt;h4&gt;Ambient Context&lt;/h4&gt;&lt;p&gt;This method should be used for universal dependencies that are being shared alongside multiple object instances, logging, and analytics of a caching mechanism. It should only be used sparingly, because it could cause implicit dependencies and global mutable state.&lt;/p&gt;&lt;h3&gt;👋 That’s all, folks!&lt;/h3&gt;&lt;p&gt;This concludes our three-part series on exploring design patterns in iOS development. If you missed the other two, or just want a refresher, go back and take a look at &lt;a href=&quot;https://lickability.com/blog/structural-design-patterns/&quot;&gt;Understanding Structural Design Patterns&lt;/a&gt; and &lt;a href=&quot;https://lickability.com/blog/understanding-behavioral-design-patterns/&quot;&gt;Understanding Behavioral Design Patterns&lt;/a&gt; for more.&lt;/p&gt; </content:encoded><author>Ashli Rankin</author></item><item><title>Tooling at Home</title><link>https://lickability.com/blog/tooling-at-home/</link><guid isPermaLink="true">https://lickability.com/blog/tooling-at-home/</guid><description>Apps we’re using to work together while apart</description><pubDate>Wed, 01 Apr 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the midst of the COVID-19 global pandemic, Lickability moved all its operations remote on March 10th. At this point, we’ve had a few weeks of working together remotely to figure out what works best for our little studio. While there are lots of great guides on how to work remote from companies like &lt;a href=&quot;https://www.notion.so/Remote-work-wiki-1b21ef5501714fffa9f5c5c25677371f&quot;&gt;Notion&lt;/a&gt;, &lt;a href=&quot;https://zapier.com/learn/remote-work/&quot;&gt;Zapier&lt;/a&gt;, and &lt;a href=&quot;https://slackhq.com/business-continuity-plan-covid-19&quot;&gt;Slack&lt;/a&gt;, we thought share some of the specific tools we’re using to make this easier for our team. Hopefully, if you’re a newly remote employee or manager, you’ll see something here that can help smooth out a part of your workflow.&lt;/p&gt;&lt;h3&gt;💬 Slack&lt;/h3&gt;&lt;p&gt;I know it’s been said many times, many ways, but Slack is great for keeping us all on the same page when we’re &lt;a href=&quot;https://slack.com/solutions/remote-work&quot;&gt;spread out physically&lt;/a&gt;. We’ve made a few small changes to our Slack Workspace that help it do that even better.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;New Statuses&lt;/strong&gt;&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d449d56c427c6d403f23ddaf0d73fd511c3453dd-1039x700.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=260 260w, https://cdn.sanity.io/images/nkt6o869/production/d449d56c427c6d403f23ddaf0d73fd511c3453dd-1039x700.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=520 520w, https://cdn.sanity.io/images/nkt6o869/production/d449d56c427c6d403f23ddaf0d73fd511c3453dd-1039x700.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=779 779w, https://cdn.sanity.io/images/nkt6o869/production/d449d56c427c6d403f23ddaf0d73fd511c3453dd-1039x700.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1039 1039w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d449d56c427c6d403f23ddaf0d73fd511c3453dd-1039x700.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1039&quot; width=&quot;1039&quot; height=&quot;700&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Our fancy new statuses in Slack.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;When we went remote, we changed the suggested status options for all of our employees in Slack to these 5 options. Now, with a quick glance in the sidebar, it‘s easy to see whether someone’s available to pair program or if they’re munching their lunch (which is, for some reason, always pancakes).&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Google Calendar Integration&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;We’re big G Suite users at Lickability, and folks in different parts of the company have meetings at lots of different times with clients all over the world. Lots of us now use the &lt;a href=&quot;https://slack.com/app-pages/google-calendar&quot;&gt;Google Calendar Slack App&lt;/a&gt; to automatically set our status to “🗓In a meeting” when there’s an event on our Google Calendars. Never be stuck wondering why someone hasn’t responded to your message again.&lt;/p&gt;&lt;h3&gt;🖥 Screen&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b72537b772c86f6e0b252987b3758498669a0832-700x1040.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=350 350w, https://cdn.sanity.io/images/nkt6o869/production/b72537b772c86f6e0b252987b3758498669a0832-700x1040.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=700 700w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b72537b772c86f6e0b252987b3758498669a0832-700x1040.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=700&quot; width=&quot;700&quot; height=&quot;1040&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;We’ve long been looking for the perfect tool for remote pair programing, since Google Hangouts is far too laggy for our engineers’ tastes.&lt;/p&gt;&lt;p&gt;It turns out that the team behind our previous favorite, Screen Hero, recently left Slack and started working on a worthy successor. It’s called &lt;a href=&quot;https://screen.so&quot;&gt;Screen&lt;/a&gt; and we’ve been using and loving the beta for a few weeks. Screen is the fastest screen sharing solution on the market, it works with multiple participants, and it even lets you drawn on or take control of the sharer’s screen if and when you need to. Plus, the team at Screen has been really receptive to our feedback and are improving the product every day. Try it out if you need to pair program at a safe social distance. It’s free while it’s in beta!&lt;/p&gt;&lt;h3&gt;🎨 Scribble&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/394f6f7d7313d9aaf7eee6be1ea8f7c53f474b09-1125x1242.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/394f6f7d7313d9aaf7eee6be1ea8f7c53f474b09-1125x1242.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/394f6f7d7313d9aaf7eee6be1ea8f7c53f474b09-1125x1242.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/394f6f7d7313d9aaf7eee6be1ea8f7c53f474b09-1125x1242.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/394f6f7d7313d9aaf7eee6be1ea8f7c53f474b09-1125x1242.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;1242&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Friends of the company &lt;a href=&quot;https://twitter.com/bridgermax&quot;&gt;Bridger Maxwell&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/mayli&quot;&gt;May-Li Khoe&lt;/a&gt; have been churning out great updates to their fun, fast, and super useful shared whiteboard app, &lt;a href=&quot;https://scribbletogether.com&quot;&gt;Scribble&lt;/a&gt;. Right now we’re using a company shared board to doodle on and make each other smile throughout the week, but we also plan to use this app (which has incredible support for the Apple pencil) for architecture meetings. With everyone in the office together, we used to stand around a physical whiteboard and diagram out solutions to technical problems—and now we have a great way to replicate that on our iPads.&lt;/p&gt;&lt;h3&gt;🐙 GitHub for Mobile&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/280827a7ade9afd8994fd70cdde6e039f4b0b972-1125x2436.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/280827a7ade9afd8994fd70cdde6e039f4b0b972-1125x2436.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/280827a7ade9afd8994fd70cdde6e039f4b0b972-1125x2436.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/280827a7ade9afd8994fd70cdde6e039f4b0b972-1125x2436.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/280827a7ade9afd8994fd70cdde6e039f4b0b972-1125x2436.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;2436&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;I like the rainbow one.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Finally, our pal &lt;a href=&quot;https://twitter.com/_ryannystrom&quot;&gt;Ryan Nystrom&lt;/a&gt; and his team at GitHub just shipped the app we’ve been waiting nearly a decade for: &lt;a href=&quot;https://github.com/mobile&quot;&gt;GitHub for Mobile&lt;/a&gt;. With this on our home screen, it’s easy to triage our GitHub notifications, review pull requests, and even merge them right from our pocket computers. The app is an incredibly polished 1.0 and we can’t wait to see even more of GitHub’s many features incorporated into this beautiful product. Check out the custom icons if you want to dress it up a bit! GitHub Mobile is available now for iOS and Android.&lt;/p&gt;&lt;p&gt;I hope that tour of some of our newest tools was remotely helpful. 😉 What are some of the tools you or your team are using to stay connected while you’re out of the office? Let us know on Twitter at &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;@lickability&lt;/a&gt;.&lt;/p&gt; </content:encoded><author>mb bischoff</author></item><item><title>Understanding Behavioral Design Patterns</title><link>https://lickability.com/blog/understanding-behavioral-design-patterns/</link><guid isPermaLink="true">https://lickability.com/blog/understanding-behavioral-design-patterns/</guid><description>A few key examples</description><pubDate>Thu, 26 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Welcome back to our &lt;strong&gt;design patterns&lt;/strong&gt; series. In &lt;a href=&quot;https://lickability.com/blog/structural-design-patterns/&quot;&gt;the first part&lt;/a&gt; of this series, we discussed six structural design patterns. In this second installment, we will be looking at behavioral patterns. Behavioral patterns allow our objects to communicate with each other, whether it be one-to-one or many-to-one.&lt;/p&gt;&lt;h3&gt;The Delegation Pattern&lt;/h3&gt;&lt;p&gt;I remember when I first heard of a delegate, introduced to me as a way of allowing objects to communicate with one another. The initial obstacle I had when working with delegates was creating a distinction between delegates and protocols.&lt;/p&gt;&lt;p&gt;A &lt;strong&gt;protocol&lt;/strong&gt; is a set of requirements that every type which conforms to it is expected to implement. A protocol contains declaration detail, not implementation details. &lt;strong&gt;Delegates&lt;/strong&gt; are implemented using protocols and are useful because they establish a one-to-one method of communication between objects. (It should be noted that in situations where a single closure can be used to communicate changes to an object, delegates may be overkill.)&lt;/p&gt;&lt;h3&gt;The Observer Pattern&lt;/h3&gt;&lt;p&gt;Unlike delegation, this pattern allows our objects to communicate in a one-to-many relationship. Here are some of the observers used in iOS:&lt;/p&gt;&lt;h4&gt;Notification Center&lt;/h4&gt;&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/nsnotificationcenter&quot;&gt;According to Apple&lt;/a&gt;, this native API is a mechanism that enables the broadcasting of information to registered observers. You should consider using this API if you have multiple objects that need to listen for the same change, without those objects having a direct connection to each other. It‘s also useful if that change has to be observed repeatedly.&lt;/p&gt;&lt;p&gt;There are a few downsides to using this pattern—for one, it is not very easy to track bugs. Unlike delegation, notifications and their observers have an indirect relationship. This can make it harder to track down where a notification is coming from or where it‘s being observed. Additionally, the object that sent the notification and the objects that listen for the changes must know about the notification &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;name&lt;/code&gt; and &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;user​Info&lt;/code&gt;. Further, there is no deterministic order to how objects listening to changes receive these notifications.&lt;/p&gt;&lt;h4&gt;Key-Value Observers&lt;/h4&gt;&lt;p&gt;With key-value observers, one type can observe the properties of another type to find out about changes to the observed type‘s state. These kinds of observers can provide an easy way for information to be synced between objects, and can provide an easy way for us to obtain a new value and the previous value of a property.&lt;/p&gt;&lt;h3&gt;What’s next&lt;/h3&gt;&lt;p&gt;This is by no means a complete list of behavioral patterns, but it‘s a good place to get started. In the third and final installment of this series, coming soon, we’ll take a look at &lt;strong&gt;creational&lt;/strong&gt; patterns.&lt;/p&gt; </content:encoded><author>Ashli Rankin</author></item><item><title>Testing Push Notifications in the iOS Simulator</title><link>https://lickability.com/blog/testing-push-notifications-in-the-simulator/</link><guid isPermaLink="true">https://lickability.com/blog/testing-push-notifications-in-the-simulator/</guid><description>Never bug a server engineer again</description><pubDate>Tue, 10 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With the release of Xcode 11.4, Apple has given us the ability to test push notifications in the simulator. Before this, testing push notifications required using a physical device and a lot of extra work. In this post, we are going to go over a few of the ways you can test push notifications on your simulator without ever having to involve an actual device, starting with the simplest path and looking at tools for more advanced uses toward the end.&lt;/p&gt;&lt;h3&gt;Initial Setup&lt;/h3&gt;&lt;p&gt;In order for any of this to work, you have to do some minor preparation. For starters, make sure you have Xcode 11.4 installed. At the time of writing this post, Xcode 11.4 beta 3 was recently released. Everything that I will display and test will be based on that version of Xcode. For more information on what’s available, check out the release notes &lt;a href=&quot;https://developer.apple.com/documentation/xcode_release_notes/xcode_11_4_beta_3_release_notes&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;If I mention Xcode, I’m referring to Xcode-beta or a version of Xcode that supports this new functionality.&lt;/p&gt;  &lt;/aside&gt;&lt;p&gt;To begin, launch Xcode and create a new project called &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Push​Test&lt;/code&gt;. It doesn’t matter if the project is based on SwiftUI or a storyboard, but I chose to use a storyboard for this example.&lt;/p&gt;&lt;p&gt;Once you have created the app, go to your &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;App​Delegate.swift&lt;/code&gt; file. Here, we are going to add the lines of code needed to display notification alerts on our device. First, lets create our &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;UN​User​Notification​Center&lt;/code&gt; object. Add this code to your &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;did​Finish​Launching​With​Options&lt;/code&gt; method:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; notificationCenter = UNUserNotificationCenter.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;current&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;This object was introduced in iOS 10 and is the central object for managing notification-related activities for our app.&lt;/p&gt;&lt;p&gt;Now we need one more line that requests authorization from the user to display our notifications:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;notificationCenter.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;requestAuthorization&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;alert&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;sound&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]) { granted, error &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // handle permissions here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Our completed code will look like this:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; application&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; application&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: UIApplication, &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;didFinishLaunchingWithOptions&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; launchOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [UIApplication.LaunchOptionsKey: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Any&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]?) -&gt; &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Bool&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Override point for customization after application launch.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; notificationCenter = UNUserNotificationCenter.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;current&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    notificationCenter.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;requestAuthorization&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;alert&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, .&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;sound&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]) { granted, error &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;        // handle permissions here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;With that in place, we have everything we need to start displaying our notifications!&lt;/p&gt;&lt;h3&gt;The Simplest Path&lt;/h3&gt;&lt;p&gt;Xcode has provided us with some “drag and drop” functionality that should make testing a breeze! Essentially, you can drag an APNS file onto your simulator and trigger a notification that way. The hardest part about that whole process is creating a properly formatted APNS file. For a guide on creating the file, check out the &lt;a href=&quot;https://developer.apple.com/documentation/xcode_release_notes/xcode_11_4_beta_3_release_notes?preferredLanguage=occ#3530440&quot;&gt;Xcode 11.4 Beta 3 Simulator New Features&lt;/a&gt;. For the sake of speed, we are going to create the file using the nano editor, but feel free to create it in your text editor of choice.&lt;/p&gt;&lt;h4&gt;Create an APNS file&lt;/h4&gt;&lt;p&gt;First, launch the terminal and type the following command:&lt;/p&gt;&lt;p&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;nano payload.apns&lt;/code&gt;&lt;/p&gt;&lt;p&gt;This will create a new file called &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;payload.apns&lt;/code&gt; and launch the nano editor. Here are Apple‘s requirements for the file:&lt;/p&gt;&lt;blockquote&gt;The file must be a JSON file with a valid Apple Push Notification Service payload, including the “aps” key. It must also contain a top-level “Simulator Target Bundle” with a string value that matches the target application‘s bundle identifier.&lt;/blockquote&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/aae8c8b19eab9be3f4d0c9dbd241d90b5bbc3129-1140x736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=285 285w, https://cdn.sanity.io/images/nkt6o869/production/aae8c8b19eab9be3f4d0c9dbd241d90b5bbc3129-1140x736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=570 570w, https://cdn.sanity.io/images/nkt6o869/production/aae8c8b19eab9be3f4d0c9dbd241d90b5bbc3129-1140x736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=855 855w, https://cdn.sanity.io/images/nkt6o869/production/aae8c8b19eab9be3f4d0c9dbd241d90b5bbc3129-1140x736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1140 1140w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/aae8c8b19eab9be3f4d0c9dbd241d90b5bbc3129-1140x736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1140&quot; width=&quot;1140&quot; height=&quot;736&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;In the screenshot above, you can see that I’ve set my “Simulator Target bundle” to the bundle ID for my app, and set my “aps” key to a very basic payload. For more information on the necessary keys to create a payload, read &lt;a href=&quot;https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW1&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Once you have added the code above press &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;CTRL x&lt;/code&gt; on your keyboard to exit the nano editor. When asked if you want to save your changes, press &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Y&lt;/code&gt; to proceed.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/cf1cc54fcef88eeb11e8d54341173ca90622be05-1142x736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=286 286w, https://cdn.sanity.io/images/nkt6o869/production/cf1cc54fcef88eeb11e8d54341173ca90622be05-1142x736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=571 571w, https://cdn.sanity.io/images/nkt6o869/production/cf1cc54fcef88eeb11e8d54341173ca90622be05-1142x736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=857 857w, https://cdn.sanity.io/images/nkt6o869/production/cf1cc54fcef88eeb11e8d54341173ca90622be05-1142x736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1142 1142w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/cf1cc54fcef88eeb11e8d54341173ca90622be05-1142x736.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1142&quot; width=&quot;1142&quot; height=&quot;736&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;When prompted for the file name you would like to save-as, press &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;enter&lt;/code&gt; to continue with the existing file name, which in our case is &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;payload.apns&lt;/code&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/8afb40feb3238ba262f0608b9fac616d3ab8c821-1140x732.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=285 285w, https://cdn.sanity.io/images/nkt6o869/production/8afb40feb3238ba262f0608b9fac616d3ab8c821-1140x732.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=570 570w, https://cdn.sanity.io/images/nkt6o869/production/8afb40feb3238ba262f0608b9fac616d3ab8c821-1140x732.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=855 855w, https://cdn.sanity.io/images/nkt6o869/production/8afb40feb3238ba262f0608b9fac616d3ab8c821-1140x732.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1140 1140w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/8afb40feb3238ba262f0608b9fac616d3ab8c821-1140x732.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1140&quot; width=&quot;1140&quot; height=&quot;732&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;🎉 That‘s it! We are now ready to test our remote notification. But first, in order to use this method, there are a few prerequisites:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;You have run your app at least once and selected &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Allow&lt;/code&gt; for the push notification permissions.&lt;/li&gt;&lt;li&gt;You have minimized your app to the background—at the moment, we don’t have code in place that would display notifications while our app is in the foreground, so you‘ll need to go to the Home screen.&lt;/li&gt;&lt;/ol&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/65c37c8b940f8833d41ad28c81fa53380836f638-586x828.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=293 293w, https://cdn.sanity.io/images/nkt6o869/production/65c37c8b940f8833d41ad28c81fa53380836f638-586x828.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=586 586w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/65c37c8b940f8833d41ad28c81fa53380836f638-586x828.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=586&quot; width=&quot;586&quot; height=&quot;828&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;There you have it! With a simple drag and drop of the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;payload.apns&lt;/code&gt; file into the simulator, the notification alert displays.&lt;/p&gt;&lt;h3&gt;Helpful Tools&lt;/h3&gt;&lt;p&gt;Up to this point, we’ve been able to test this feature with very little work. The hardest part so far is making sure the formatting is correct for your &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;payload.apns&lt;/code&gt; file. Once that is correct, testing a push notification is as simple as dragging the file onto your simulator. However, since the announcement of this feature, there have been a few tools created to help with this process.&lt;/p&gt;&lt;h4&gt;Poes&lt;/h4&gt;&lt;p&gt;One of those tools is called &lt;a href=&quot;https://github.com/AvdLee/Poes&quot;&gt;Poes&lt;/a&gt;. Poes is a Swift command-line tool that helps you send push notifications to the simulator. Once you have it installed, sending a push notification is as simple as typing the following command:&lt;/p&gt;&lt;p&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;poes send com.marc.Push​Test&lt;/code&gt;&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b94ffd043da5a2082c88b3e69c3334a4949898e7-640x454.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=320 320w, https://cdn.sanity.io/images/nkt6o869/production/b94ffd043da5a2082c88b3e69c3334a4949898e7-640x454.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=640 640w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b94ffd043da5a2082c88b3e69c3334a4949898e7-640x454.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=640&quot; width=&quot;640&quot; height=&quot;454&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;I am using my bundle identifier above. You’ll need to replace that with the bundle identifier for your app when you are testing.&lt;/p&gt;  &lt;/aside&gt;&lt;p&gt;The cool thing about this tool is that you no longer need to worry about trying to format your JSON payload. Via the command line arguments, you can provide the text and body of your push notification. In the gif above, you can see that I‘m just using the default values provided by Poes for the body and title of the push notification.&lt;/p&gt;&lt;p&gt;I really like the simplicity and ease-of-use of Poes. For more information, check out the creator &lt;a href=&quot;https://www.avanderlee.com&quot;&gt;Antoine van der Lee’s&lt;/a&gt; blog—especially his &lt;a href=&quot;https://www.avanderlee.com/workflow/testing-push-notifications-ios-simulator/&quot;&gt;post&lt;/a&gt; about using it for push notification testing.&lt;/p&gt;&lt;h4&gt;Control Room&lt;/h4&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/twostraws/ControlRoom&quot;&gt;Control Room&lt;/a&gt; is the Tesla of tools at the moment, in my humble opinion. Control Room is a macOS app written by &lt;a href=&quot;https://twitter.com/twostraws&quot;&gt;Paul Hudson&lt;/a&gt; that lets you control the UI appearance, status bar configuration, and more for iOS, tvOS, and watchOS simulators. It wraps Apple’s own &lt;code index=&quot;4&quot; isInline=&quot;true&quot;&gt;simctl&lt;/code&gt; command-line tool, so you’ll need Xcode installed. The thing about this tool is that it serves multiple purposes, while also providing a notification editor that lets you build your notification payload.&lt;/p&gt;&lt;p&gt;To use Control Room, clone the GitHub repo to your PC and run the Xcode project. If you already have a simulator running, you can use the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Default&lt;/code&gt; option to run your tests. Simply select your application‘s bundle identifier in the list of options and press the “Send Push Notification” button.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;This test assumes that you have already run your project at least once, accepted the push notification permissions dialog, and backgrounded your app.&lt;/p&gt;  &lt;/aside&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/286e7c5af07cfc1b9967cb06e7e94832629ac761-640x350.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=320 320w, https://cdn.sanity.io/images/nkt6o869/production/286e7c5af07cfc1b9967cb06e7e94832629ac761-640x350.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=640 640w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/286e7c5af07cfc1b9967cb06e7e94832629ac761-640x350.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=640&quot; width=&quot;640&quot; height=&quot;350&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;As you can see above, it’s really easy to send notifications using Control Room. If you press the “Open Notification Editor” button, you can customize your notifications further, including providing data for rich notifications. While these are all items you could do with Apple’s &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;simctl&lt;/code&gt; command-line tool, Control Room allows you to do all of it in a GUI editor.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/4472c3bb4759944eb29e46bb53cccbb0ab384da2-1724x1490.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/4472c3bb4759944eb29e46bb53cccbb0ab384da2-1724x1490.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/4472c3bb4759944eb29e46bb53cccbb0ab384da2-1724x1490.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/4472c3bb4759944eb29e46bb53cccbb0ab384da2-1724x1490.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/4472c3bb4759944eb29e46bb53cccbb0ab384da2-1724x1490.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1724 1724w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/4472c3bb4759944eb29e46bb53cccbb0ab384da2-1724x1490.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1383&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;TL;DR&lt;/h3&gt;&lt;p&gt;In this post, we created a basic project that we used to test remote notifications. We looked at the simplest path, which involved dragging an &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;apns&lt;/code&gt; file onto our simulator without having to install any dependencies or use separate tools. This method works very well for quickly testing and does not require any specific tools or versions of macOS.&lt;/p&gt;&lt;p&gt;On the other hand, we also discussed Poes and Control Room as alternative tools for testing. Both of these tools are written in Swift which is another plus. Poes is a command-line tool that lets you send a push notification in one line via the terminal, and Control Room is a macOS app that lets you control your simulator in various ways while also doing things like sending a remote notification. In general, I really like this new feature in Xcode, and it can be used in all sorts of different scenarios.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Want to build an iOS app? We can help! &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Talk to us&lt;/a&gt;.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Marc Aupont</author></item><item><title>A Lawyer and an Accountant</title><link>https://lickability.com/blog/a-lawyer-and-an-accountant/</link><guid isPermaLink="true">https://lickability.com/blog/a-lawyer-and-an-accountant/</guid><description>The two pros every company needs to hire</description><pubDate>Wed, 15 Jan 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Starting Lickability &lt;a href=&quot;https://lickability.com/blog/10-years-of-lickability/&quot;&gt;10 years ago&lt;/a&gt; was relatively simple on paper. As 19-year-olds, Brian and I hopped on &lt;a href=&quot;https://www.legalzoom.com/&quot;&gt;LegalZoom&lt;/a&gt;, filled out some form fields, and presto—our company was born. If you’re thinking of starting a company today, tools like &lt;a href=&quot;https://stripe.com/atlas&quot;&gt;Stripe Atlas&lt;/a&gt; make it even easier. But at a certain point of seriousness and profitability, you can’t get away with outsourcing all of your corporate administration to a robot.&lt;/p&gt;&lt;p&gt;Once your company is off the ground, there comes a point when you need to hire professionals to help manage the bureaucratic mazes of contracts and taxes. Today, I’m going to tell you about the two most important professionals to hire, how to do it, and the folks we recommend working with in case you need a referral. 😉&lt;/p&gt;&lt;h3&gt;Hire a Lawyer&lt;/h3&gt;&lt;p&gt;Software is complex. As developers, it’s our job to wrangle that complexity and present something easily understandable to our customers so they can get their job done. A good lawyer will do that for you with a similarly byzantine system: the law. As Emily Croy Barker pointed out in her &lt;a href=&quot;https://www.nytimes.com/2013/07/28/opinion/sunday/the-rules-of-magic.html?fbclid=IwAR3LL3xDS_O-BD8ArGrRNlGzfb1-8zE3GmPy_EQgiFhZLO3Dfz2akTEUX-w&quot;&gt;New York Times opinion piece in 2013&lt;/a&gt;, it’s not absurd to think of lawyers like wizards. She writes, “In the end, magic is another form of power, and lawyers understand power. That’s what they do. The law regulates the powers of different parties — of the state, of private individuals and of corporate bodies — and a lawyer’s task is to find ways to enhance, exploit or rein in those powers.”&lt;/p&gt;&lt;p&gt;Before Lickability had a lawyer, we were piecing our contracts together from Google searches, boilerplate template libraries, and favors from friends who were smart enough to have already hired a lawyer. That all changed when we watched this now-famous &lt;a href=&quot;https://creativemornings.com/talks/mike-monteiro--2/1&quot;&gt;Creative Mornings talk by Mike Monteiro&lt;/a&gt;, one of the founders of Mule Design in San Francisco. In the talk, Mike appears with his lawyer (and friend) to answer questions from the crowd about how to get paid for their creative work. Mike says that he loves his lawyer because he saves them money. This was a lightbulb moment for us years ago. We always thought of lawyers as a costly expense, rather than an investment in the company’s bottom line.&lt;/p&gt;&lt;blockquote&gt;If you have a great lawyer that understands software, I’d love a recommendation. Mentions and DMs work. Thanks!&lt;br&gt;&lt;br&gt;— mb Bischoff (@mb) &lt;a href=&quot;https://twitter.com/mb/status/627144330914983937?ref_src=twsrc%5Etfw&quot;&gt;July 31, 2015&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;After that, we asked Twitter for recommendations, had a few initial conversations, and eventually hired Greg and his firm &lt;a href=&quot;https://www.mavrolaw.com/&quot;&gt;Mavronicolas Law Group&lt;/a&gt; via our pal Kyle Bragger. Over the last five years, Greg and his team have helped us negotiate dozens of contracts, filed our trademarks, and kept an eye out for our interests in many ways throughout the years. We all sleep better and do better because of their partnership. As soon as your company starts facing even minor contractual or legal questions, invest in a great lawyer. It’ll pay off in spades.&lt;/p&gt;&lt;h3&gt;Hire an Accountant&lt;/h3&gt;&lt;p&gt;With the contractual out of the way, let’s discuss the financial. As your company starts to make some cash, you’ll need to maintain your books, file, and pay your taxes. If you’re like Lickability, with founders living in multiple states and clients all over the US, this can get complicated really fast.&lt;/p&gt;&lt;p&gt;We went through a number of accountants before settling on the one that makes the most sense for us. Andrew Carroll (aka &lt;a href=&quot;https://cfoandrew.com/&quot;&gt;CFO Andrew&lt;/a&gt;) was recommended to us by friend of the company, &lt;a href=&quot;https://soff.es/&quot;&gt;Sam Soffes&lt;/a&gt;. What’s different about Andrew’s model is that he isn’t just there for you at tax time; he partners with freelancers and companies year-round.&lt;/p&gt;&lt;p&gt;Andrew has taught us a lot over the years: the difference between accrual and cash based accounting, passthrough income vs. C corporations, and how much cash we should keep in the bank to run the business. And because he’s also a financial advisor, we can ping him in his Slack with random personal or corporate finance questions whenever they strike throughout the year.&lt;/p&gt;&lt;p&gt;If your company is already making a profit, it’s time to hire an accountant. You’ll be glad you did when they help save you money with tax credits, deductions, and avoiding fees by filing correctly and on time.&lt;/p&gt;&lt;h3&gt;Bonus: Hire an Ops Person&lt;/h3&gt;&lt;p&gt;If you’re at the size where you’ve already got a lawyer and accountant you love, consider all the administrative and operational stuff you deal with every week that makes it harder for you to run the company. If it’s enough work that it’s bugging you, it might be worth talking to someone who‘s really good at dealing with operational and administrative tasks. Before we hired &lt;a href=&quot;https://twitter.com/jilliangmeehan&quot;&gt;our own Operations Associate&lt;/a&gt;, we worked part-time with the wonderful and hyper-organized &lt;a href=&quot;https://www.theunicornsidekick.com&quot;&gt;Kathy Campbell&lt;/a&gt;. Give her a ring, and she’ll sort out all your admin.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;Your job as a founder or CEO is to run your company and make your customers happy, not to do every little thing by yourself. Work with the pros who will save you time and make you money. Hire a lawyer, hire an accountant, and (if you’ve got room in the budget) get some help with your operations. Trust me, you‘ll be glad you did.&lt;/p&gt; </content:encoded><author>mb bischoff</author></item><item><title>Our Favorite Apps of 2019</title><link>https://lickability.com/blog/our-favorite-apps-of-2019/</link><guid isPermaLink="true">https://lickability.com/blog/our-favorite-apps-of-2019/</guid><description>New apps we loved this year</description><pubDate>Wed, 08 Jan 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We haven‘t shared &lt;a href=&quot;https://lickability.com/blog/home-sweet-home-screen/&quot;&gt;our favorite apps&lt;/a&gt; with you in a while, so as we begin the new year, this feels like a good time to give a shout-out to a few of our recent favorites from 2019. Some of these apps were new to the App Store last year, and some were just new to us, but all of them are fantastic.&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/356476aa24253a1721b00e0c8d816ddfc65f09f0-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/356476aa24253a1721b00e0c8d816ddfc65f09f0-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/356476aa24253a1721b00e0c8d816ddfc65f09f0-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/356476aa24253a1721b00e0c8d816ddfc65f09f0-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=780 780w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/356476aa24253a1721b00e0c8d816ddfc65f09f0-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;✈️ Flighty&lt;/h3&gt;&lt;p&gt;Friend of Lickability Ryan Jones and his team released &lt;a href=&quot;https://www.flightyapp.com&quot;&gt;Flighty&lt;/a&gt; this year, and it is hands down one of the best apps for frequent fliers. The best push notifications in the business, delay forecasting, and a design that feels pro and fits perfectly on iOS. If you fly more than a few times of year, get Flighty. You can thank me when you get to your destination.&lt;/p&gt;&lt;p&gt;&lt;em&gt;– mb Bischoff&lt;/em&gt;&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a629409ba790f536f5e1f35aa51239c66bc57452-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/a629409ba790f536f5e1f35aa51239c66bc57452-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/a629409ba790f536f5e1f35aa51239c66bc57452-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/a629409ba790f536f5e1f35aa51239c66bc57452-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=780 780w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a629409ba790f536f5e1f35aa51239c66bc57452-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;🤖 Apollo for Reddit&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://apps.apple.com/us/app/apollo-for-reddit/id979274575&quot;&gt;Apollo&lt;/a&gt; is my most used app and my go-to when I want to get info on breaking news, keep tabs on the communities I care about, or just browse some dank memes. I really enjoy the user experience and it is my preferred way to view and interact with reddit content. The developer &lt;a href=&quot;https://twitter.com/ChristianSelig&quot;&gt;Christian&lt;/a&gt; frequently interacts with the community and is constantly releasing updates and new features. I started with Apollo Pro and upgraded to Apollo Ultra this year to unlock some cool features and support the ongoing development.&lt;/p&gt;&lt;p&gt;&lt;em&gt;– Tom DeVuono&lt;/em&gt;&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/ae095331a1196d83159728551b3a83d2b6371fd1-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/ae095331a1196d83159728551b3a83d2b6371fd1-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/ae095331a1196d83159728551b3a83d2b6371fd1-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/ae095331a1196d83159728551b3a83d2b6371fd1-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=780 780w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/ae095331a1196d83159728551b3a83d2b6371fd1-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;📖 We Read Too&lt;/h3&gt;&lt;p&gt;My wife and I regularly use &lt;a href=&quot;https://apps.apple.com/us/app/we-read-too/id908782619&quot;&gt;We Read Too&lt;/a&gt; to find new books for my son to read, and it helps a lot with discovery. Plus, it‘s written by &lt;a href=&quot;https://kayathomas.info&quot;&gt;Kaya Thomas&lt;/a&gt;, who is also awesome!&lt;/p&gt;&lt;p&gt;&lt;em&gt;– Marc Aupont&lt;/em&gt;&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/72bfc89d6f451e4f880f3cac838332f531ff7332-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/72bfc89d6f451e4f880f3cac838332f531ff7332-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/72bfc89d6f451e4f880f3cac838332f531ff7332-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/72bfc89d6f451e4f880f3cac838332f531ff7332-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=780 780w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/72bfc89d6f451e4f880f3cac838332f531ff7332-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;🃏 Card of Darkness&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://apps.apple.com/us/app/card-of-darkness/id1364257627&quot;&gt;Card of Darkness&lt;/a&gt; launched with Apple Arcade, and despite the deluge of quality iOS games hitting the service simultaneously, I couldn’t shift my focus away from finishing every last level. The rules and mechanics are simple enough to pick up right away, but become difficult enough to stay interesting throughout. And the art. THE ART. Pendleton Ward’s involvement had me hooked from the very beginning.&lt;/p&gt;&lt;p&gt;&lt;em&gt;– Michael Liberatore&lt;/em&gt;&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/04f4c7687d2e7b6ecb48fcec03ab1ad0d6ae7547-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/04f4c7687d2e7b6ecb48fcec03ab1ad0d6ae7547-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/04f4c7687d2e7b6ecb48fcec03ab1ad0d6ae7547-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/04f4c7687d2e7b6ecb48fcec03ab1ad0d6ae7547-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=780 780w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/04f4c7687d2e7b6ecb48fcec03ab1ad0d6ae7547-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;🎵 Sayonara Wild Hearts&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://apps.apple.com/us/app/sayonara-wild-hearts/id1441675161&quot;&gt;Sayonara Wild Hearts&lt;/a&gt;, available via Apple Arcade, is a music album and a rhythm video game in one. I really enjoy the music, and I love rhythm games, so it‘s perfect for me. Plus, it’s very queer and the art style is pleasing to me.&lt;/p&gt;&lt;p&gt;&lt;em&gt;– Michael Amundsen&lt;/em&gt;&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/bb5936b9f8092f1869ef5c6b0fa46d61a1e40b37-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/bb5936b9f8092f1869ef5c6b0fa46d61a1e40b37-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/bb5936b9f8092f1869ef5c6b0fa46d61a1e40b37-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/bb5936b9f8092f1869ef5c6b0fa46d61a1e40b37-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=780 780w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/bb5936b9f8092f1869ef5c6b0fa46d61a1e40b37-780x780.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;🧠 Youper&lt;/h3&gt;&lt;p&gt;2019 was the year I got really into mood tracking, both as a way to journal and to check in on my mental health on a regular basis. &lt;a href=&quot;https://itunes.apple.com/us/app/id1060691513&quot;&gt;Youper&lt;/a&gt; has made a big difference in the way I recognize and process my emotions, and it‘s become an essential part of my daily routine. Every day, I tell the app how I’m feeling and what external factors are contributing to my mood, and it helps me walk through why I might be feeling the way I’m feeling.&lt;/p&gt;&lt;p&gt;&lt;em&gt;– Jillian Meehan&lt;/em&gt;&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>A New(er) Look for The Atlantic</title><link>https://lickability.com/blog/a-new-er-look-for-the-atlantic/</link><guid isPermaLink="true">https://lickability.com/blog/a-new-er-look-for-the-atlantic/</guid><description>How we built the redesigned app</description><pubDate>Wed, 11 Dec 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Everyone loves receiving a good grade. Seeing a giant, red “A” at the top of an assignment is such a great feeling—in fact, that‘s the same grade we would give The Atlantic on the new look they debuted a few weeks ago. They redesigned their brand from top to bottom—with a &lt;a href=&quot;https://www.theatlantic.com/news/archive/2019/11/introducing-new-look-atlantic/601762/&quot;&gt;bold, red “A” logo&lt;/a&gt; to match—and in the process took a whole new look at how they approach news on mobile. Naturally, we jumped at the chance to help them build it.&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/1687a59a43238bc1725e302efae42e059a6f37ec-888x892.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/1687a59a43238bc1725e302efae42e059a6f37ec-888x892.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/1687a59a43238bc1725e302efae42e059a6f37ec-888x892.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/1687a59a43238bc1725e302efae42e059a6f37ec-888x892.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/1687a59a43238bc1725e302efae42e059a6f37ec-888x892.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;402&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;The Atlantic 6.0 may seem a bit familiar to users of the previous version—the general structure is similar, but it also includes some huge changes. The most vital update is on the very first screen, which has a completely new &lt;em&gt;Today&lt;/em&gt; tab: a guide to the stories that matter each day, with thoughts and articles curated by a team of mobile-specific editors. It is a confident and unique approach to presenting the day‘s news, focused specifically on readers with devices in their pockets. This shift coincided with a full rebranding of The Atlantic, with new fonts, icons, colors, story presentations, article layouts, and account management, among just some of the updates.&lt;/p&gt;&lt;p&gt;We built The Atlantic to feel right at home on iOS 13. With the design team at The Atlantic, we worked to make every screen look great in dark mode, including dark variants for images on story lists. If you‘ve logged into The Atlantic on Safari, we auto-fill your information when logging into the app with iCloud Keychain. Articles should load faster and saving your position within them should be more reliable. And, for our fellow nerds: under the hood we’re even making use of Apple‘s all-new &lt;a href=&quot;https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/compositional_layout_objects&quot;&gt;compositional layouts&lt;/a&gt; and &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource&quot;&gt;diffable data sources&lt;/a&gt; in addition to &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uinavigationbar/3198028-standardappearance&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;standard​Appearance&lt;/code&gt;&lt;/a&gt; to provide a solid foundation for future work. (Want some help making sense of them all? &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Get in touch&lt;/a&gt;!)&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/141de7c8870e7ba553e001e59df80b5fc6deeb2f-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=331 331w, https://cdn.sanity.io/images/nkt6o869/production/141de7c8870e7ba553e001e59df80b5fc6deeb2f-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=663 663w, https://cdn.sanity.io/images/nkt6o869/production/141de7c8870e7ba553e001e59df80b5fc6deeb2f-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=994 994w, https://cdn.sanity.io/images/nkt6o869/production/141de7c8870e7ba553e001e59df80b5fc6deeb2f-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325 1325w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/141de7c8870e7ba553e001e59df80b5fc6deeb2f-1325x2616.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1325&quot; width=&quot;1325&quot; height=&quot;2616&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/46d3c646838cbd2e9b1f74113e36b824661e7f04-1443x2867.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=361 361w, https://cdn.sanity.io/images/nkt6o869/production/46d3c646838cbd2e9b1f74113e36b824661e7f04-1443x2867.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=722 722w, https://cdn.sanity.io/images/nkt6o869/production/46d3c646838cbd2e9b1f74113e36b824661e7f04-1443x2867.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1082 1082w, https://cdn.sanity.io/images/nkt6o869/production/46d3c646838cbd2e9b1f74113e36b824661e7f04-1443x2867.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1443 1443w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/46d3c646838cbd2e9b1f74113e36b824661e7f04-1443x2867.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1443&quot; width=&quot;1443&quot; height=&quot;2867&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Screenshots of the new Atlantic app&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;We also fundamentally believe that people of all abilities should have the opportunity to comfortably use the software we build, especially when it comes to journalism. The Atlantic made accessibility a priority on this project, and we worked hard to make the iOS app completely accessible, from full VoiceOver and Voice Control support to thoughtful support for Smart Invert preserving image colors. We checked every color in the app through Apple‘s accessibility tool to make sure the colors included in the app contrast each other for maximum legibility, and we included both system and in-app control for dynamic type sizes.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/07646ff91b93574f8cf6be21d1b5f43a9b4571bf-671x328.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=336 336w, https://cdn.sanity.io/images/nkt6o869/production/07646ff91b93574f8cf6be21d1b5f43a9b4571bf-671x328.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=671 671w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/07646ff91b93574f8cf6be21d1b5f43a9b4571bf-671x328.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=671&quot; width=&quot;671&quot; height=&quot;328&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;It was a pleasure working with our friends at The Atlantic &lt;a href=&quot;https://lickability.com/blog/a-new-look-for-the-atlantic-app/&quot;&gt;once again&lt;/a&gt; on this project. We are so proud of the new release, and it was only possible due to their strong vision and smart decisions about where the company is headed. We were able to move quickly, work with the latest technologies, and make an app that looks and functions in a modern way, thanks to their team of smart and kind people. The Atlantic has a whole new look, but more importantly it has a new approach to news on mobile, and we hope that you love it as much as we do.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://itunes.apple.com/app/id397599894&quot;&gt;Download the app&lt;/a&gt; today to see it all in action!&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Need some assistance making or updating your app? &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Reach out&lt;/a&gt; and see how we can help.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Brian Capps</author></item><item><title>Understanding Structural Design Patterns</title><link>https://lickability.com/blog/structural-design-patterns/</link><guid isPermaLink="true">https://lickability.com/blog/structural-design-patterns/</guid><description>What they are, and how to use them</description><pubDate>Fri, 06 Dec 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you are like me and have a year or less of experience developing iOS apps, there might be some concepts that you use in code without realizing what they are or why you‘ve used them—I’m talking about &lt;strong&gt;design patterns.&lt;/strong&gt; These patterns allow our app‘s codebase to be more readable and resilient, help guide us when structuring our apps, and allow us to create objects and govern how those objects communicate with each other. Chances are, you’ve used many of these patterns without realizing it—like &lt;a href=&quot;https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/Model-View-Controller/Model-View-Controller.html#targetText=Roles%20and%20Relationships%20of%20MVC%20Objects,and%20their%20lines%20of%20communication&quot;&gt;MVC&lt;/a&gt;, &lt;a href=&quot;https://www.raywenderlich.com/34-design-patterns-by-tutorials-mvvm&quot;&gt;MVVM&lt;/a&gt;, &lt;a href=&quot;https://developer.apple.com/documentation/swift/cocoa_design_patterns/managing_a_shared_resource_using_a_singleton&quot;&gt;Singleton&lt;/a&gt;, and &lt;a href=&quot;https://developer.apple.com/documentation/swift/cocoa_design_patterns/using_key-value_observing_in_swift&quot;&gt;Observer&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Design patterns are quite powerful if you know what to use and when to use it. I set out on a journey to explore some of the various design patterns used in iOS development and gain a better understanding of them.&lt;/p&gt;&lt;p&gt;This is the first of a three-part series on design patterns. In this installment, we will be looking at structural patterns. Structural patterns allow us to more easily design the relationships of our application components. Below, we‘ll take a look at six structural patterns, with a code example for each one. Let’s dive in together!&lt;/p&gt;&lt;h3&gt;🔌 The Adapter Pattern&lt;/h3&gt;&lt;p&gt;This pattern is similar to using adapters in our daily life—for example, if you want to connect a USB-A cable to a USB-C port, you need an adapter to change the connector type of the USB-A cable into one the USB-C port can recognize. Similarly, we can use the adapter pattern to allow incompatible interfaces to communicate with each other. We can also use it when working with third-party libraries that are incompatible with our existing code.&lt;/p&gt;&lt;p&gt;The adapter pattern is beneficial because it abstracts business logic to make our interfaces work together more seamlessly, and allows for the ability to introduce new kinds of adapters without breaking existing code. The drawback, however, is that it increases code complexity by introducing a set of new interfaces and classes.&lt;/p&gt;&lt;h3&gt;🖌 The Decorator Pattern&lt;/h3&gt;&lt;p&gt;This pattern allows us to add functionality to an object without changing its fundamental components. It‘s like getting dressed—we change our outfits daily, but these changes have no effect on our composition as humans.&lt;/p&gt;&lt;p&gt;The decorator pattern allows us to extend the behavior of objects without making a new subclass. Further, we can divide a large class that implements many behaviors into several smaller ones.&lt;/p&gt;&lt;h3&gt;🎂 The Facade Pattern&lt;/h3&gt;&lt;p&gt;I think of the facade pattern as a bakery: so many things happen behind the scenes at a bakery, but when a customer comes in, they only see the beautifully designed pastries and cakes.&lt;/p&gt;&lt;p&gt;This pattern is designed to provide a simplified interface to a complex subsystem. It‘s like Paul Hudson says in his book &lt;a href=&quot;https://www.hackingwithswift.com/store/swift-design-patterns&quot;&gt;Swift Design Patterns&lt;/a&gt;:&lt;/p&gt;&lt;blockquote class=&quot;quote-block&quot;&gt; &lt;p class=&quot;quote-text&quot;&gt;A Good Facade:
- Makes complex APIs easier to read, understand, and use, because their complexity is wrapped up in simpler methods.
- Reduces dependencies on the internals of those APIs, which allows you to change them freely as long as the end result does the same job.
- Lets you wrap a bad architecture in something that works better based on experience, or a complex architecture in something simpler that does the job for most people.&lt;/p&gt; &lt;cite class=&quot;quote-author&quot;&gt;Paul Hudson&lt;/cite&gt; &lt;/blockquote&gt;&lt;h3&gt;🎨 Model View Controller&lt;/h3&gt;&lt;p&gt;This is the first pattern I learned when I started iOS development. It involves separating app components into models, views, and controllers.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Models&lt;/strong&gt; are a representation of our underlying data, containing all the properties and methods which define a type of object; &lt;strong&gt;views&lt;/strong&gt; are responsible for displaying information from the controller; and the &lt;strong&gt;controller&lt;/strong&gt; is responsible for mediating views and models. When the user interacts with a view, the controller interprets this information and alerts the model to update accordingly.&lt;/p&gt;&lt;p&gt;While using this pattern, I‘ve run into things in my applications that fall into a grey area, like networking logic—where would that go? Once you start asking yourself questions like that, you should consider reaching for another design pattern.&lt;/p&gt;&lt;p&gt;Below is an example of the MVC pattern in action. We have our model, which represents an &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Ice​Cream&lt;/code&gt;; our controller, containing an array of ice cream flavors; and our view, which takes the information given by the controller and displays it.&lt;/p&gt;&lt;h3&gt;🖼 Model-View-ViewModel (MVVM)&lt;/h3&gt;&lt;p&gt;One limitation to the MVC pattern is that controllers can get quite large—often jokingly called “Massive View Controller.” This occurs because when we have responsibilities that don‘t fall into our model, view, or controller, they tend to end up in our view controller.&lt;/p&gt;&lt;p&gt;The MVVM pattern has been proposed as an alternative, encapsulating our components into models, views, and view models. Like with MVC, &lt;strong&gt;models&lt;/strong&gt; are our data representations; &lt;strong&gt;views&lt;/strong&gt; can be our &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;UI​View​Controller&lt;/code&gt; and our &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;UI​View&lt;/code&gt; subclasses; and &lt;strong&gt;view models&lt;/strong&gt; are used to transform data from our model into a form that can be used by our views.&lt;/p&gt;&lt;h3&gt;👩‍🎨 Model-View-ViewModel-Controller (MVVMC)&lt;/h3&gt;&lt;p&gt;We employ the MVVMC pattern heavily at Lickability. Before I started, I was unfamiliar with this pattern, but after using it for a few months I have found it to be quite helpful when structuring my applications. I‘ve noticed three major differences between this pattern and MVC: improved code encapsulation, improved code readability, and smaller controllers as a result of removing some presentation logic.&lt;/p&gt;&lt;p&gt;When I used to use the MVC pattern, I found myself exposing view objects in order to configure them, not yet aware of the dangers of doing so (like outside entities being able to change the content displayed on these objects). With the MVVMC pattern, each view has its own &lt;a href=&quot;https://lickability.com/blog/our-view-on-view-models/&quot;&gt;view model&lt;/a&gt; encapsulating the data needed to populate the view, and is the single point of configuration of this view. By doing this, our objects no longer need to have an internal level.&lt;/p&gt;&lt;h3&gt;🔮 What‘s next&lt;/h3&gt;&lt;p&gt;Whew! That was a lot. But this just covers a small selection of all the design patterns out there. In future blog posts, we‘ll take a look at a few other types of design patterns: &lt;strong&gt;behavioral&lt;/strong&gt; patterns, and &lt;strong&gt;creational&lt;/strong&gt; patterns. Stay tuned!&lt;/p&gt; </content:encoded><author>Ashli Rankin</author></item><item><title>How Our Engineers Collaborate</title><link>https://lickability.com/blog/how-our-engineers-collaborate/</link><guid isPermaLink="true">https://lickability.com/blog/how-our-engineers-collaborate/</guid><description>There’s no “I” in “iOS”...wait</description><pubDate>Tue, 26 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Collectively, the engineers at Lickability have worked with dozens of iOS development teams, shipping countless features, new products, redesigns, and Swift rewrites. &lt;a href=&quot;https://blog.lickability.com/10-years-of-lickability-7101641bc9a4&quot;&gt;Ten years in&lt;/a&gt;, our processes have evolved into a well-oiled machine with &lt;a href=&quot;https://blog.lickability.com/the-value-of-values-3b2e2ee5328b&quot;&gt;collaboration&lt;/a&gt; at the core of our day-to-day engineering. Whether we’re augmenting a client’s existing iOS team or acting as the sole developers, and whether we’re building a social network, news reader, or game, our approach to collaborating on code remains largely the same. We find it important to follow a few key guidelines on every product we help ship, and would like to share them with you.&lt;/p&gt;&lt;h3&gt;The 30 Minute Rule&lt;/h3&gt;&lt;p&gt;Have you ever been stuck trying to fix a bug that seemed extremely straightforward? Have you ever been knee-deep into a new feature, only to find that something just isn’t working correctly and you’re not sure why? Have you ever felt paralyzed while trying to architect a large feature or refactor, spinning your wheels because you’re not sure if you’re making the right decisions? Of course you have! We’ve all been there, and we’ll be there again. In situations like this, we reach for the &lt;strong&gt;30 Minute Rule.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Have you spent 30 minutes trying to solve the problem with no visible progress, and are you able to describe what you’ve tried and what happened? If the answer is &lt;em&gt;yes&lt;/em&gt;, then it’s time to ask a teammate for help. If you keep spinning your wheels for hours and hours, you might be wasting your own time. If you haven’t done your 30 minutes of due diligence before reaching out to others, you might be prematurely interrupting and wasting their time. Our magic number is 30, but feel free to experiment and see what works best for your team.&lt;/p&gt;&lt;p&gt;Frequently asking for help might be hard at first, especially if you’re on a team that hasn’t established a pattern of assisting each other in this way. It can be extremely difficult to fight through self-doubt or imposter syndrome and admit that you can’t proceed quickly without aid. I know. I’ve been there. It’s not just you. But asking someone for help doesn’t make you less-than. After all, you’re a team working toward the same goal, not competing against one another.&lt;/p&gt;&lt;p&gt;Be sure to pay it forward when you can, and reach out to others when they appear halted. It might be that one weird UIKit issue that you know exactly how to work around. They might just need to talk through their approach to make sure it’s not too off-the-wall. Or they might find comfort in the fact that there’s &lt;em&gt;not&lt;/em&gt; an obvious, simple solution, and it’s &lt;em&gt;not&lt;/em&gt; just them. Once your teammates become comfortable asking for help and assisting each other regularly, productivity will soar and you’ll be shipping with confidence.&lt;/p&gt;&lt;h3&gt;Talk Before You Code&lt;/h3&gt;&lt;p&gt;Sure, sometimes you can just sit down in front of your computer, read a feature or bug description, and hit the ground running. But for larger or more complicated tasks, it can be invaluable to discuss your path forward with another member of your team. Whether or not you prefer to pair program, talking over architecture, or proposing a solution on a whiteboard before you write a line of code can help to shake out any uncertainties, expose previously unconsidered cases, and give your team the opportunity to weigh in and reduce complexity long before opening a pull request.&lt;/p&gt;&lt;p&gt;You might worry that others on your team don’t have enough context to help at this stage. After all, only you have dug deep into this problem. Ignore those thoughts! Like you, the engineers on your team have diverse experience and have likely helped solve similar problems in the past. Just prepare your thoughts, quickly bring them up to speed about the task, and summarize your approach. At Lickability, we do this All. The. Time. And most of us aren’t even working on the same project! Keep things high level at this stage, and your fellow engineers should be able to help you out.&lt;/p&gt;&lt;h3&gt;Make Your Work Easy to Review&lt;/h3&gt;&lt;p&gt;Code review is an extremely important part of working on a software team. It provides the opportunity for feedback, collaboration, and an early line of defense against issues entering your shipping software. Some find that code review is a chore, but it doesn’t have to be.&lt;/p&gt;&lt;h4&gt;Small Pull Requests&lt;/h4&gt;&lt;p&gt;Long running branches are a recipe for disaster and conflicts, full-stop. Many teams don’t need to be sold on the benefits of &lt;a href=&quot;https://en.wikipedia.org/wiki/Continuous_integration&quot;&gt;continuous integration&lt;/a&gt;, and with modern tools like &lt;a href=&quot;https://fastlane.tools/&quot;&gt;fastlane&lt;/a&gt; and &lt;a href=&quot;https://www.bitrise.io/&quot;&gt;Bitrise&lt;/a&gt;, a lot of the boilerplate work can easily be automated. But no software is going to force you to integrate early and often, and no one wants to review your 10,000 line pull request. Have you ever submitted a gigantic pull request and received one comment, “LGTM”? This is not the feedback you want. Have you ever submitted a gigantic pull request and it just sat there, rotting at the bottom of the PR list, surrounded by other large pull requests that were also increasing in the number of conflicts with &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;main&lt;/code&gt;? This is surprisingly common, and we try to do everything we can to prevent it.&lt;/p&gt;&lt;p&gt;Now, have you ever fixed a small bug, or introduced a small new feature, and received 16 comments with helpful suggestions? It’s not a coincidence that the size of the pull request produced different results from your peers.&lt;/p&gt;&lt;p&gt;The &lt;a href=&quot;https://smallbusinessprogramming.com/optimal-pull-request-size/&quot;&gt;optimal pull request size&lt;/a&gt; will differ for every team, but at Lickability, we encourage engineers to keep them small (500 lines changed or less), and to the point. Sure, sometimes they end up slightly larger, but we try to minimize that by following these guidelines:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Don’t fix 8 bugs in one pull request. Fix 1.&lt;/li&gt;&lt;li&gt;Keep things that generate a lot of noise in code review, like pod updates and adding assets, in their own, separate pull requests that precede the work that requires them.&lt;/li&gt;&lt;li&gt;If you’re working on a large feature, break it up into many small chunks, and submit your work whenever you complete one. Use any form of &lt;a href=&quot;https://en.wikipedia.org/wiki/Feature_toggle&quot;&gt;feature flagging&lt;/a&gt; to integrate these chunks while preventing the exposure of incomplete features to users.&lt;/li&gt;&lt;li&gt;Try not to go more than a day or two before putting your work up for review. Just because your changes are small doesn’t mean they weren’t difficult or time consuming to produce. The earlier you receive feedback, the less married to that solution you’ll be. It can be hard to accept criticism on something you’ve poured a ton of time into.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;You’ll be surprised how much more meaningful your code reviews will be when your pull requests are small and numerous. Someone can review and fully understand your work in the few minutes before a meeting instead of needing to set aside an afternoon of head scratching.&lt;/p&gt;&lt;h4&gt;Explain Your Work&lt;/h4&gt;&lt;p&gt;I could go on for hours about the value of documenting your code, but even when code &lt;em&gt;is&lt;/em&gt; properly documented, it can be difficult to wrap your head around why a change is being introduced, or how it impacts the user experience. We rely heavily on &lt;a href=&quot;https://help.github.com/en/articles/about-issue-and-pull-request-templates&quot;&gt;pull request templates&lt;/a&gt; to ensure engineers provide enough information to ensure a speedy and easy review. (You can find ours &lt;a href=&quot;https://github.com/Lickability/.github/blob/master/.github/pull_request_template.md&quot;&gt;here&lt;/a&gt;.)&lt;/p&gt;&lt;p&gt;Whether or not you use a template, we believe a pull request description should contain:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A link to the issue or task in your issue tracking system so that the reviewer can understand why the change is being proposed.&lt;/li&gt;&lt;li&gt;A short summary of the approach taken to fix the issue or implement the feature.&lt;/li&gt;&lt;li&gt;Detailed steps on how to test the change. This will help the reviewer test the feature themselves if they choose to, but also give them the opportunity to see how &lt;em&gt;you&lt;/em&gt; tested the feature, and suggest any gaps or edge cases you might’ve missed.&lt;/li&gt;&lt;li&gt;A screenshot, gif, or video of the change in action, if it has an effect on the user experience.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Here are &lt;a href=&quot;https://github.com/Lickability/PinpointKit/pull/214&quot;&gt;a&lt;/a&gt; &lt;a href=&quot;https://github.com/Lickability/PinpointKit/pull/244&quot;&gt;few&lt;/a&gt; &lt;a href=&quot;https://github.com/Lickability/PinpointKit/pull/237&quot;&gt;examples&lt;/a&gt; from our open-source library PinpointKit.&lt;/p&gt;&lt;h3&gt;Teamwork == Dreamwork&lt;/h3&gt;&lt;p&gt;If you’ve been writing software for a while, and prefer to work alone, or have been affected by any of the problems discussed here, I encourage you to give some of these guidelines a shot. You don’t have to go all in at once and completely change your workflows. You don’t need the world’s most advanced server-configurable feature flagging system to get started with small PRs. And most of all, you have no reason to feel badly or inadequate if you ask for help. If you’re fortunate enough to work with a team of talented engineers, make the most of it. You and your teammates can do amazing things together.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Do you like our approach to engineering? Work with us! We‘re available to help build or update your iOS app—just &lt;a href=&quot;https://lickability.com/contact&quot;&gt;get in touch&lt;/a&gt;.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Michael Liberatore</author></item><item><title>Our New Blog</title><link>https://lickability.com/blog/our-new-blog/</link><guid isPermaLink="true">https://lickability.com/blog/our-new-blog/</guid><description>From Medium to our .com</description><pubDate>Wed, 20 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Notice anything different? Welcome to the brand new &lt;a href=&quot;https://lickability.com/blog&quot;&gt;Lickability blog&lt;/a&gt;! In the past two months, we took a bit of a break from publishing new posts while we moved all of our content over to our website from Medium. From now on, all of our new blog posts will live here, on &lt;strong&gt;Lickability.com&lt;/strong&gt;.&lt;/p&gt;&lt;h3&gt;What we did&lt;/h3&gt;&lt;p&gt;I’ll give you the TL;DR version of what we did. We downloaded our archive from Medium and used &lt;a href=&quot;https://github.com/Donohue/medium-to-jekyll&quot;&gt;Brian Donohue‘s script&lt;/a&gt; to convert those posts to &lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt; posts that we could use on our website. We spent some time making everything look nice, added blog topics like &lt;a href=&quot;https://lickability.com/blog/categories/engineering&quot;&gt;Engineering&lt;/a&gt; and &lt;a href=&quot;https://lickability.com/blog/categories/how-we-work&quot;&gt;How We Work&lt;/a&gt; and &lt;a href=&quot;https://lickability.com/blog/categories/what-s-new&quot;&gt;What‘s New&lt;/a&gt;, and used the &lt;a href=&quot;https://github.com/jekyll/jekyll-redirect-from&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;jekyll-redirect-from&lt;/code&gt;&lt;/a&gt; plugin to redirect all of our old blog links to the new ones. Then, we set up &lt;a href=&quot;https://www.siteleaf.com&quot;&gt;Siteleaf&lt;/a&gt;—a CMS that was “built for developers”—for editing and publishing new blog posts, like this one.&lt;/p&gt;&lt;h3&gt;Why we did it&lt;/h3&gt;&lt;p&gt;Medium &lt;a href=&quot;https://help.medium.com/hc/en-us/articles/115003053487-Custom-Domains-service-deprecation&quot;&gt;announced&lt;/a&gt; a few years ago that they were deprecating support for custom domains, like ours. Since then, we’ve been thinking about moving our blog so we can have more control over our content.&lt;/p&gt;&lt;p&gt;Medium fit our needs for a long time, but it was time for a change. As we continue to put more energy into our blog and publish more posts, it felt natural to move away from Medium. We want our blog to feel more like &lt;em&gt;us&lt;/em&gt;, and making space for it on our own website goes a long way toward achieving that.&lt;/p&gt;&lt;p&gt;If you’ve been reading our blog for a while, thank you! And if you’re new around here, welcome. We have a bunch of blog posts ready for you—we hope you like them. 😊&lt;/p&gt; </content:encoded><author>Jillian Meehan</author></item><item><title>Conference Condensed: XOXO 2019</title><link>https://lickability.com/blog/conference-condensed-xoxo-2019/</link><guid isPermaLink="true">https://lickability.com/blog/conference-condensed-xoxo-2019/</guid><description>Why we ❤️ it, and why you will too</description><pubDate>Sun, 29 Sep 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At the beginning of this month, mb and Jillian went to Portland for &lt;a href=&quot;https://blog.lickability.com/conferences-we-know-and-love-9fe6bb78b5c&quot;&gt;another year&lt;/a&gt; of &lt;a href=&quot;https://xoxofest.com/&quot;&gt;XOXO&lt;/a&gt;, the “experimental festival for independent artists” that we look forward to every year. It was, predictably, fantastic. We could keep this blog post short and tell you to just &lt;em&gt;go&lt;/em&gt;, but in case you need some more convincing, we each wrote about some of our favorite parts from this year’s XOXO.&lt;/p&gt;&lt;h3&gt;Social&lt;/h3&gt;&lt;p&gt;&lt;em&gt;mb&lt;/em&gt;&lt;/p&gt;&lt;p&gt;As the Andys reminded us on the first day of the festival, &lt;a href=&quot;https://xoxofest.com/2019/schedule/social&quot;&gt;&lt;em&gt;Social&lt;/em&gt;&lt;/a&gt; started as a happy accident in the first year of the conference and has only grown in importance since then. A day of socializing with other attendees of self-organized meetups all over SE Portland, it’s one of the best parts of the weekend. While Jillian was on her way to the city, I debated between a packed schedule of meetups with thoughtful conscientious nerds. Starting at the #enby meetup at &lt;a href=&quot;https://upperleftroasters.com/&quot;&gt;Upper Left Roasters&lt;/a&gt;, I met up with a bunch of other well-dressed nonbinary folks who effortlessly used each others pronouns (speaking of which, XOXO’s adorable &lt;a href=&quot;https://buyolympia.com/Artist/XOXO&quot;&gt;pronouns pins are now on sale&lt;/a&gt;). After that, I headed over to the &lt;a href=&quot;https://appy-hour.glitch.me/&quot;&gt;Glitch Appy Hour&lt;/a&gt; where I drank some LaCroix and saw an incredible demo from &lt;a href=&quot;http://caswenson.com/about.html&quot;&gt;Christopher Swenson&lt;/a&gt; who ported a ’90s multiplayer BBS game to Glitch! At 6 PM I met some more LGBTQ+ tech and art folks at the #queer meetup, held at &lt;a href=&quot;https://www.crushbar.com/&quot;&gt;Crush&lt;/a&gt;. The #cocktails nerds came together for a toast at the brand new bar &lt;a href=&quot;https://scotchlodge.com/&quot;&gt;Scotch Lodge&lt;/a&gt;. And finally, Jillian found me at the &lt;a href=&quot;https://play.date/&quot;&gt;Panic Playdate Party&lt;/a&gt; back on the lawn at Revolution Hall where we got to get our hands on the &lt;a href=&quot;https://play.date/&quot;&gt;much-anticipated console&lt;/a&gt; from &lt;a href=&quot;https://panic.com/&quot;&gt;our favorite Mac software company&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The meetups I attended as part of XOXO Social just scratched the surface of what the festival has to offer. There were also meetups for fans of #language, #visual-art, #game-making, #zines, and so much more. But, I was so glad I got to meet so many incredible folks and see old friends before jumping into the conference proper. People like &lt;a href=&quot;https://twitter.com/lasermwebber?lang=en&quot;&gt;Laser Malena-Webber&lt;/a&gt; of &lt;a href=&quot;https://www.thedoubleclicks.com/&quot;&gt;The Doubleclicks&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/isaacgrant?lang=en&quot;&gt;Isaac Grant&lt;/a&gt; (the CEO of &lt;a href=&quot;https://www.silverorange.com/&quot;&gt;Silverorange&lt;/a&gt;), my good pal &lt;a href=&quot;https://allenpike.com/&quot;&gt;Allen Pike&lt;/a&gt; from &lt;a href=&quot;https://steamclock.com/&quot;&gt;Steamclock Software&lt;/a&gt;, and &lt;a href=&quot;http://mrgan.com/&quot;&gt;Neven Mrgan&lt;/a&gt; and &lt;a href=&quot;https://cabel.com/&quot;&gt;Cabel Sasser&lt;/a&gt; plus more of the team behind Playdate.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/7a18da39d0d82b02ebc1f313b2c62277e766b850-1600x1600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/7a18da39d0d82b02ebc1f313b2c62277e766b850-1600x1600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/7a18da39d0d82b02ebc1f313b2c62277e766b850-1600x1600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/7a18da39d0d82b02ebc1f313b2c62277e766b850-1600x1600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/7a18da39d0d82b02ebc1f313b2c62277e766b850-1600x1600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1600&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;An art installation outside the XOXO venue&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;Arcade&lt;/h3&gt;&lt;p&gt;&lt;em&gt;Jillian&lt;/em&gt;&lt;/p&gt;&lt;p&gt;As much as I love the conference talks, the live shows, and allllllll the socializing at XOXO, I always find myself gravitating toward the &lt;em&gt;Arcade&lt;/em&gt; tent and its live video game demos the most. Maybe because it’s slightly removed from the rest of the action and acts as a nice respite from everything when my energy is a bit low. Maybe because I find it somewhat soothing to be around people who are playing games. Or maybe it’s simply because the games that are showcased are always just really cool and fun. Whatever the reason, &lt;em&gt;Arcade&lt;/em&gt; is generally my favorite part of XOXO, and this year was no exception.&lt;/p&gt;&lt;p&gt;A few of the games that especially caught my eye this year were &lt;a href=&quot;http://home.calicogame.com/&quot;&gt;Calico&lt;/a&gt;, an extremely cute and cozy cat cafe simulator; &lt;a href=&quot;https://www.moonlightkids.co/the-wild-at-heart&quot;&gt;The Wild at Heart&lt;/a&gt;, a beautiful, magical action-adventure game; and &lt;a href=&quot;https://store.steampowered.com/app/1062140/Garden_Story/&quot;&gt;Garden Story&lt;/a&gt;, a fruit-themed RPG that I cannot stop thinking about. And, although not technically part of the Arcade showcase, I can’t not mention the &lt;a href=&quot;https://play.date/&quot;&gt;Playdate&lt;/a&gt; here as well — it’s very good, folks.&lt;/p&gt;&lt;h3&gt;Conference&lt;/h3&gt;&lt;p&gt;&lt;em&gt;mb&lt;/em&gt;&lt;/p&gt;&lt;p&gt;I look forward to the Conference section of XOXO all year long. Its presenters are thoughtfully selected from suggestions made by the community and represent a diverse set of perspectives and mediums. This year’s conference was hosted by podcasters &lt;a href=&quot;https://twitter.com/HelenZaltzman&quot;&gt;Helen Zaltzman&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/HrishiHirway&quot;&gt;Hrishikesh Hirway&lt;/a&gt; and included talks from incredible creators, writers, artists, musicians, and comedians. Here are a few of my favorites and the themes I saw emerge this year. In their talks, &lt;a href=&quot;https://twitter.com/thelindsayellis&quot;&gt;Lindsay Ellis&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/emilynagoski&quot;&gt;Emily&lt;/a&gt; &amp;amp; &lt;a href=&quot;https://www.goodreads.com/author/show/17693646.Amelia_Nagoski&quot;&gt;Amelia Nagoski&lt;/a&gt;, and &lt;a href=&quot;https://twitter.com/the_jennitaur&quot;&gt;Jenny Odell&lt;/a&gt; touched on themes of burn out, empathy, and how we can disconnect from the parts of the internet that are hurting us and connect with communities that support us through hard times online. As the Nagoski sisters said in the summary of their new book: “the cure for burnout is not self care; it’s all of us caring for each other.”&lt;/p&gt;&lt;p&gt;Other speakers like Nat Puff (aka &lt;a href=&quot;https://twitter.com/LeftAtLondon&quot;&gt;Left at London&lt;/a&gt;), &lt;a href=&quot;https://twitter.com/blackbelteagles&quot;&gt;Black Belt Eagle Scout&lt;/a&gt;, and &lt;a href=&quot;https://twitter.com/hooleil&quot;&gt;Soleil Ho&lt;/a&gt; asked us to consider different sides of the questions of representation, marginalization, and what happens when you become the representation you’ve been fighting for. Their talks were great reminders that seeing people like you making things on the internet, makes a huge difference to young and up-coming creative people. And that once we have that representation, we must maintain that power and find a way to remain who we are. As Soleil put it: “we are the grease in each other’s engines.”&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/1ca871dc4d1333e3845eec06f4230dfe85755d5d-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/1ca871dc4d1333e3845eec06f4230dfe85755d5d-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/1ca871dc4d1333e3845eec06f4230dfe85755d5d-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/1ca871dc4d1333e3845eec06f4230dfe85755d5d-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/1ca871dc4d1333e3845eec06f4230dfe85755d5d-1600x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Jillian and mb fawning over the &lt;a href=&quot;https://play.date/&quot;&gt;Playdate&lt;/a&gt;. Photo by Kate Sloan.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;Video &amp;amp; Story&lt;/h3&gt;&lt;p&gt;&lt;em&gt;Jillian&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Every year, XOXO does us the great service of taking some of the best video and podcast creators and putting them on a stage to show us their latest work. Whether that’s a new video they haven’t shared online yet or a live podcast taping, it’s always a blast. &lt;em&gt;Video&lt;/em&gt; and &lt;em&gt;Story&lt;/em&gt;, to me, really encapsulate what XOXO is all about: engaging with and celebrating the things people make in a friendly, intimate environment — it’s like Twitter, but good.&lt;/p&gt;&lt;p&gt;This year, I particularly enjoyed watching new videos from &lt;a href=&quot;https://www.youtube.com/watch?v=JiQLHL10BYo&quot;&gt;the Nerdwriter&lt;/a&gt;, and &lt;a href=&quot;https://www.youtube.com/watch?v=9Jcxc-ddWKI&amp;list=PLaDrN74SfdT7Ueqtwn_bXo1MuSWT0ji2w&amp;index=17&amp;t=0s&quot;&gt;Unraveled&lt;/a&gt;. &lt;a href=&quot;https://www.theallusionist.org/&quot;&gt;The Allusionist&lt;/a&gt; did a fantastic live show about gender and the confusion of titles. And &lt;a href=&quot;https://punchupthejam.com/&quot;&gt;Punch Up the Jam&lt;/a&gt; stole the show with a hilarious episode about Ghostbusters, &lt;a href=&quot;http://twitter.com/electrolemon&quot;&gt;Demi’s&lt;/a&gt; last episode of the podcast that I’m extremely glad we got to witness.&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Conference Condensed: try! Swift 2019</title><link>https://lickability.com/blog/conference-condensed-try-swift-2019/</link><guid isPermaLink="true">https://lickability.com/blog/conference-condensed-try-swift-2019/</guid><description>A “Swift” recap of one of our favorite iOS events 😉</description><pubDate>Mon, 23 Sep 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you’re in the NYC iOS community and you aren’t following &lt;a href=&quot;https://www.tryswift.co/&quot;&gt;try! Swift&lt;/a&gt;, what are you even doing? It’s one of our favorite iOS conferences, especially for folks who are new to Swift. This year, as we did last year, we sponsored a diversity ticket and sent a few volunteers from Lickability to attend. Plus, our very own Marc Aupont gave his first conference talk ever, and it was a hit. Here are a few more of our favorite memories from try! Swift 2019:&lt;/p&gt;&lt;h3&gt;😱 SwiftUI in 25 Minutes&lt;/h3&gt;&lt;p&gt;Try! Swift was my first conference ever, and I was not disappointed. One of the highlights would have to be &lt;a href=&quot;https://twitter.com/twostraws&quot;&gt;Paul Hudson&lt;/a&gt;’s “SwiftUI in 25 minutes” talk. I had never seen Paul speak, but I have used his Swift guides extensively. If I could only use one word to describe this talk, it would be &lt;em&gt;thrilling&lt;/em&gt;. It was, in my opinion, the greatest sales pitch of all time. He reintroduced us to a product that could make our lives as developers significantly better, and demonstrated how robust the framework is by implementing various features that common applications have, like table views and scroll views. I was sold the moment I saw how easy it is to work with animations. As a plus, he showed us how to make your app cross-platform using SwiftUI — it was as simple as clicking a button. Watching this talk ignited the same fire I felt when SwiftUI was unveiled at WWDC 2019. What an amazing time to be a developer! And as if that wasn’t enough, Paul even gave us a gift: &lt;a href=&quot;https://www.hackingwithswift.com/quick-start/swiftui&quot;&gt;&lt;em&gt;SwiftUI By Example&lt;/em&gt;&lt;/a&gt;, a 242-page PDF. I can now confidently say my life is complete.&lt;/p&gt;&lt;p&gt;&lt;em&gt;– Ashli Rankin&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;💸 App Subscriptions — The Good, The Bad, and The Ugly&lt;/h3&gt;&lt;p&gt;One of my favorite parts of try! Swift each year is the type of talk that poses an interesting question for me to think about and look into more after the conference is over. &lt;a href=&quot;https://twitter.com/ishabazz&quot;&gt;Ish Shabazz&lt;/a&gt;’s talk about subscriptions was a great example of this. During the talk, he went through the various models developers can use to generate revenue for their apps — payment upfront, ads, free, in-app purchases, subscriptions — and the various pros and cons to each model. He also compared the various revenue models against overall lifetime cost of developing and maintaining your app. Naturally, subscriptions have a lot of pros to them, and generally are the best option for offsetting long term costs of an app. So here’s a summary of the good, the bad, and the ugly parts of subscriptions:&lt;/p&gt;&lt;h4&gt;The Good&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Recurring revenue&lt;/li&gt;&lt;li&gt;More profit after the first year&lt;/li&gt;&lt;li&gt;Future updates are funded&lt;/li&gt;&lt;li&gt;Offers, promos, and free trials&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;The Bad&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Tricky to implement and test&lt;/li&gt;&lt;li&gt;Schools may not be able to buy them and no family sharing&lt;/li&gt;&lt;li&gt;A lot of users really don’t like them&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;The Ugly&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Can be difficult to cancel&lt;/li&gt;&lt;li&gt;Subscription management is confusing and luckily better in iOS 13&lt;/li&gt;&lt;li&gt;No refunds&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;What really struck me was how, over time, most revenue models do not help pay for development, and users want free updates for life. It sounds kind of obvious, but Ish’s talk has stuck with me as an interesting challenge we face as developers. How will we adapt to help value the time and effort that goes into software development?&lt;/p&gt;&lt;p&gt;&lt;em&gt;– Michael Amundsen&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;🌟 Swift Without Screens — Powering Connected Devices&lt;/h3&gt;&lt;p&gt;Try! Swift is definitely one of my favorite conferences to attend. The participants are amazingly friendly, and the variety of talks and speakers is great. In 2017, I was fortunate enough to receive a diversity scholarship to attend for the first time. During that time, I met &lt;a href=&quot;https://twitter.com/bugkrusha&quot;&gt;Jazbo&lt;/a&gt;, one of that year’s hosts, and we became good friends. I was very much inspired by him, because I had never seen a person of color on stage at a tech conference. I found out that he was a diversity scholarship recipient the year before, and had convinced his company to offer two more diversity scholarships to try! Swift. That very moment, I knew I wanted to follow in his footsteps someday.&lt;/p&gt;&lt;p&gt;Fast forward a couple of years later — I gave my first conference talk at try! Swift this year while working for a company that sponsored a diversity ticket! I was also fortunate enough to host a workshop where we built a small electronic circuit and controlled it via Swift and Raspberry Pi.&lt;/p&gt;&lt;p&gt;&lt;em&gt;– Marc Aupont&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;📱 Swift UI for Production&lt;/h3&gt;&lt;p&gt;I really enjoyed the Swift UI for Production talk by &lt;a href=&quot;https://twitter.com/hellosunschein&quot;&gt;Lea Marolt&lt;/a&gt;. I was extremely excited about Swift UI when it was announced at WWDC19, but because of the multitude of breaking changes, I had slightly fallen out of love with it. Lea’s talk demonstrated the successes and challenges faced while building a SwiftUI app for production at &lt;a href=&quot;https://www.raywenderlich.com/&quot;&gt;RayWenderlich.com&lt;/a&gt;. She compared SwiftUI vs UIKit and went through concrete examples of components they built.&lt;/p&gt;&lt;p&gt;&lt;em&gt;– Marc Aupont&lt;/em&gt;&lt;/p&gt;&lt;p&gt;This is just a small selection of the many informative, thoughtful talks and workshops featured during the two days of try! Swift. If you didn’t get a chance to attend this year, we hope you’ll &lt;a href=&quot;https://www.tryswift.co/&quot;&gt;consider going next year&lt;/a&gt;. And if you see us there, don’t forget to say hi! 👋&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>What I learned as an iOS Apprentice</title><link>https://lickability.com/blog/what-i-learned/</link><guid isPermaLink="true">https://lickability.com/blog/what-i-learned/</guid><description>My journey into tech</description><pubDate>Tue, 17 Sep 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There is a saying that goes, “It is not about the destination, but the journey.” This is certainly true — it does the mind good to reflect on the experiences encountered on the way to your chosen destination. My journey into and through the tech world has been a rollercoaster, but I believe that learning from other people’s experiences has anchored me in my desire to work in tech as a young, determined woman forging a name for herself in this field. So here is a gift from me to those of you who are on a similar journey.&lt;/p&gt;&lt;p&gt;My name is Ashli Rankin, and I am an iOS Apprentice here at &lt;a href=&quot;https://lickability.com/&quot;&gt;Lickability&lt;/a&gt;. Before this, I was part of a 10-month intensive software development program called &lt;a href=&quot;https://www.pursuit.org/&quot;&gt;Pursuit&lt;/a&gt;, which gave me the skills to land my first job in tech.&lt;/p&gt;&lt;p&gt;This apprenticeship has been one of the most challenging yet rewarding experiences of my life. It has been challenging to try to solve problems with the knowledge I have already acquired, and to acquire and hone new skills along the way. It has been rewarding to watch my coding skills improve, to learn how to make more conscious decisions in my work, and to see how truly beneficial working with an awesome team can be. I am now well over halfway through the process, and I want to take time to reflect on the experience and share my reflections with you.&lt;/p&gt;&lt;h3&gt;💬 Don’t be afraid to ask questions&lt;/h3&gt;&lt;p&gt;Early on in my apprenticeship, I was afraid to ask questions because I didn’t want to look like I had no clue what I was doing. Now, I remind myself that it’s common for new developers to feel that way, and I force myself to ask the question anyway. It is better to have tried and failed than to not try at all. Asking questions also improves your productivity — why let a problem stump you for days when you could save time by asking for help? By asking questions, you also gain valuable insights into how other people solve problems, which is always a good thing. So ask away!&lt;/p&gt;&lt;h3&gt;📈 Keep improving&lt;/h3&gt;&lt;p&gt;One of my favorite life mantras is, “Keep striving for better.” No matter how many times I fail, I always reassess the situation and try to find a way to do better. There is always room for improvement, but it takes consistency and a desire to be better than the person you were yesterday. That said, owning your accomplishments is important too. I have a hard time recognizing my accomplishments because of my belief that I could always do better — but if I am always chasing the idea of perfection, when will I reach the finish line? Taking small steps to reflect on each pull request and project I have worked on has helped me tremendously to value the work I am doing, thus providing the push I need to do better. At the end of each week, I look through all of the issues I’ve resolved, review the changes I made, and note the reasons behind each change. By doing this, I save valuable time by consciously reminding myself to avoid making the same mistakes over and over, being mindful of best practices, and asking my teammates questions so I can continue to learn.&lt;/p&gt;&lt;h3&gt;💡 Have confidence in your abilities&lt;/h3&gt;&lt;p&gt;The desire to keep improving can result in increased levels of confidence. With this confidence, it is important to believe in yourself and stand by your choices. Sometimes, when writing code, I’ve been asked about why I made certain decisions. At the beginning of my apprenticeship, I wasn’t always confident enough to answer those questions. That has been a teaching moment for me — I learned to defend my choices, because I made them for a reason. I started using a method that I call “&lt;strong&gt;conscious coding&lt;/strong&gt;.” This entails asking yourself questions: &lt;em&gt;What am I trying to do? Is the code I’m writing affected by other parts of the application? What depends on this code?&lt;/em&gt; If I can answer questions like these, I am more equipped to answer questions that may arise on my pull requests, and more confident in my work overall.&lt;/p&gt;&lt;h3&gt;🙌 Trust the process&lt;/h3&gt;&lt;p&gt;I used to get extremely frustrated when I tried something new and it didn’t work. It took a while for me to accept the rarity of getting something right on the first try — it’s only through practice that I can get better at something. Ultimately, I’ve learned to trust the process. My first time implementing OAuth, I looked for tutorials where people attempted to use the same API I was trying to use. However, no one seemed to be using the APIs from the company that I needed data from. I looked for tutorials explaining the process of OAuth 2.0, which was somewhat helpful — I was one step closer to cracking the puzzle, but I still was not able to authenticate my user. I went home and kept trying. Eventually, at 1:45 am, I had to ask myself some deep questions: &lt;em&gt;What are you doing? What are you trying to do? Do you think stressing yourself out will magically solve this?&lt;/em&gt;&lt;/p&gt;&lt;p&gt;And then, I answered myself: &lt;em&gt;You need to pace yourself. Break this problem into smaller executable parts, and you will see how easy it can become.&lt;/em&gt; The next day, I went to work with a renewed attitude toward the project, and it only took me another day to complete both the authentication and authorization areas of OAuth2.0. All I needed to do was trust the process and take my time. Remember: everything happens in its own time and for a reason.&lt;/p&gt;&lt;h3&gt;🧠 Take care of yourself&lt;/h3&gt;&lt;p&gt;If you had the year I’ve had, you would understand why this journey is so important to me. I put my all into a 10-month intensive program with many sleepless nights and very few breaks, coupled with working 11-hour shifts on the weekends. In the midst of it all, I never took the time to check in with myself to make sure I was okay. Despite my packed schedule, I enjoyed working hard, because it gave me a sense of accomplishment. I was surviving on the passion for creating apps and learning as much as I could. At the time, I didn’t feel like I was burned out — but I definitely felt it when I completed the program.&lt;/p&gt;&lt;p&gt;When I began my apprenticeship, there were days when I left work with the intention of working on a new project at night. I would get home, start it, and…&lt;em&gt;bam!&lt;/em&gt; One hour in, I was asleep. After a week of this, I realized that my body was trying to send me a message to take better care of myself. I didn’t know where to start, so I asked my teammate Jillian for help, and she gave me the most solid piece of advice I had heard in a while: take things slow, be consistent, and don’t overwhelm yourself. Since then, I’ve taken her advice to heart. I am now slowing down and taking small steps to look after myself. My wellness and wellbeing became a focus of my consciousness. I began taking time during my commute to read and listen to music that makes me feel good — I’ve found that it really helps to calm any feelings of anxiety. What works for someone else might be different, but the important thing is bringing self care into the equation. If you feel good about yourself, world order is restored.&lt;/p&gt;&lt;p&gt;I hope my words will give you some encouragement. While we all struggle with the obstacles life throws at us, the way we deal with those deterrents determines how we progress in life. Once you get over those hurdles, you may realize that the journey wasn’t so bad after all. The key to remaining positive and upbeat in the workplace is to love what you are doing — I love what I do, and I enjoy what I do. If you find that contentment, as I have done, you can conquer the world of tech.&lt;/p&gt; </content:encoded><author>Ashli Rankin</author></item><item><title>Conference Condensed: AnxietyTech</title><link>https://lickability.com/blog/conference-condensed-anxiety-tech/</link><guid isPermaLink="true">https://lickability.com/blog/conference-condensed-anxiety-tech/</guid><description>Let&apos;s talk about mental health</description><pubDate>Tue, 03 Sep 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few weeks ago, we had the pleasure of checking out AnxietyTech, an NYC conference that explores mental health and self care in the tech industry. We had a great time and learned a lot, and it’s definitely on our list of favorite tech conferences to attend. Here are a few of our favorite talks from the day:&lt;/p&gt;&lt;h3&gt;🧠 How To Hack Our Brains Into Better Health&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Dr. Jud. Brewer&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The day started out with a fascinating Keynote presentation by &lt;a href=&quot;https://drjud.com/about/&quot;&gt;Dr. Jud Brewer&lt;/a&gt;, the Director of Research and Innovation at the Mindfulness Center at Brown University. Dr. Jud spoke about his research on how to combat the rising tide of clinical anxiety. Having looked into the research on anti-anxiety medication, willpower, and self-control, he realized that there is a better way to treat both anxiety and disordered eating with the help of technology. Using reward and awareness techniques from research on operant conditioning delivered through applications, he’s been able to show a 57% reduction in anxiety and a 50% reduction in feelings of burnout among the folks he’s treated. This presentation really got us thinking about how anxiety is built through negative feedback loops and that those same loops can be re-purposed in order to treat it. If you’re interested in reading more of Dr. Jud’s research you can find it on &lt;a href=&quot;https://drjud.com/blog/&quot;&gt;his blog&lt;/a&gt; and find his &lt;a href=&quot;https://drjud.com/#app-programs&quot;&gt;app-based treatment programs&lt;/a&gt; here.&lt;/p&gt;&lt;h3&gt;❤️ I’m Not Well and That’s Okay&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Julia Nguyen&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://julianguyen.org/&quot;&gt;Julia Nguyen&lt;/a&gt; gave a very personal presentation about the technology industry and its impact on her mental health. She astutely pointed out that our industry often centers “impact” to the exclusion of humanity and self-care, and often stigmatizes and silences marginalized groups like those from LGBTQ+ &amp;amp; BIPOC communities. Julia’s contribution to these problems include her successful open-source project, &lt;a href=&quot;https://www.if-me.org/&quot;&gt;If-Me&lt;/a&gt;, which lets people privately discuss their mental health with friends and loved ones. The project also maintains a list of other resources, including &lt;a href=&quot;https://aloebud.com/&quot;&gt;Aloe Bud&lt;/a&gt;, an app we helped create that focuses on self-care reminders. Julia left us with questions to ask ourselves whenever we take on a new mission-driven project, including: &lt;em&gt;Are they practicing what they preach? How are queer/trans/POC being left out?&lt;/em&gt; She also reminded us that even therapists need therapists, and to take care of ourselves so we don’t suffer from the same burnout we’re working against.&lt;/p&gt;&lt;h3&gt;💬 UX With Mental Health in Mind&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Bradley Gabr-Ryn&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.notion.so/Bradley-Gabr-Ryn-132374af105c40ed90c2dfe8de0dd615&quot;&gt;Bradley Gabr-Ryn&lt;/a&gt;, a product designer at Facebook, explored the ways UX design can impact our mental health in his talk. He pointed out common digital hooks that drive growth and engagement in social platforms — things like retweets and the algorithms we know and love (love to hate, at least) — and how those hooks make it harder to walk away from social media, which leads to overconsumption and reliance. His talk focused on a few ways we can begin to fix this overconsumption: make room to talk and learn about mental health in the workplace, value people’s time, stop calling the people using your product “users,” and be as transparent, comprehensible, and clear with your product’s intentions as possible. The bottom line of this talk? “Don’t be icky” — in other words, treat your customers like people, and do your best to design a product that prioritizes their mental health, rather than engagement at all costs.&lt;/p&gt;&lt;h3&gt;💻 Hacking Your Emotional API&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;John Sawers&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://johnksawers.com/index.html&quot;&gt;John Sawers&lt;/a&gt; helped us put emotions in a context developers can better relate to by asking us to imagine our brains are wired to an API that triggers certain feelings. He guided us through four levels of emotional techniques to help us better understand and “refactor” these feelings. These four levels include concepts like controlling your own implementation by deciding where and when to process your feelings, &lt;a href=&quot;https://rubberduckdebugging.com/&quot;&gt;rubber duck debugging&lt;/a&gt; to help you solve your problems by explaining them to someone else, doing emotional retros to regularly check in on your feelings, and learning to address your feelings with other people one-on-one or in group settings. More information about this talk, including the slides, can be found on the &lt;a href=&quot;https://emotionalapi.com/&quot;&gt;Emotional API website&lt;/a&gt;.&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Swift on Raspberry Pi</title><link>https://lickability.com/blog/swift-on-raspberry-pi/</link><guid isPermaLink="true">https://lickability.com/blog/swift-on-raspberry-pi/</guid><description>Controlling hardware is easy as Pi</description><pubDate>Sun, 25 Aug 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://www.raspberrypi.org/&quot;&gt;Raspberry Pi&lt;/a&gt; is a small, cost-effective, single-board computer with potential only limited by its computing power. It is also well-known by hobbyists and technologists alike. It’s the perfect device for those looking to experiment with an idea or test the validity of a concept. It can fit almost anywhere and be used for a variety of projects — it can be mounted on the back of a monitor and used as a desktop computer, or connected to a breadboard to control an electronic circuit.&lt;/p&gt;&lt;p&gt;The official programming language of Raspberry Pi is Python. While Python is fairly easy to pick up, it lacks features like type safety and has &lt;a href=&quot;https://dzone.com/articles/python-memory-issues-tips-and-tricks&quot;&gt;high memory consumption&lt;/a&gt;. Swift, on the other hand, has memory management through ARC, and is over 8x faster. Since Raspberry Pi is limited in RAM and CPU power, using a language like Swift is perfect for maximizing the potential of the hardware. Understanding how hardware and software work together is crucial for determining the efficiency of your next project. In this guide, we’ll cover the basics of getting started with Swift on Raspberry Pi.&lt;/p&gt;&lt;h3&gt;Hardware vs. software&lt;/h3&gt;&lt;p&gt;When it comes to the devices we rely on every day, there are usually two components: the hardware and the software. &lt;strong&gt;Hardware&lt;/strong&gt; is the physical object we interact with, and &lt;strong&gt;software&lt;/strong&gt; is what controls that hardware’s behavior.&lt;/p&gt;&lt;p&gt;Let’s use the iPhone as an example. The hardware includes things like the GPS, Bluetooth, OLED screen, gyroscope, camera, and more. But in order to interact with that hardware, we need software — in this case, iOS. And as a layer on top of that operating system, we have the Swift programming language, which allows developers to create applications that run within the operating system to control the hardware.&lt;/p&gt;&lt;p&gt;On Raspberry Pi, hardware like a camera or GPS is attached separately via chips or electrical components. In order to control these components, we can install libraries that support that hardware. A majority of the available components can be controlled via libraries written in Swift. We’re going to explore how to set up Swift on a Raspberry Pi to control those components.&lt;/p&gt;&lt;h3&gt;Installing an OS&lt;/h3&gt;&lt;p&gt;Before we can install Swift on Raspberry Pi, we first need to pick an operating system. There are &lt;a href=&quot;https://www.raspberrypi.org/downloads/&quot;&gt;several third-party options&lt;/a&gt; to choose from, but the most common choice is &lt;strong&gt;Raspbian&lt;/strong&gt;, the officially supported OS of Raspberry Pi. There are multiple ways to install Raspbian onto an SD card — we’re going to use &lt;strong&gt;balenaEtcher&lt;/strong&gt;. Here are the steps:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Download the latest Raspbian OS &lt;a href=&quot;https://downloads.raspberrypi.org/raspbian_latest&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.admfactory.com/how-to-format-usb-flash-drive-to-fat32-in-mac-os/&quot;&gt;Format your SD card to MS-DOS (FAT) using Disk Utility&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Use &lt;a href=&quot;https://www.balena.io/etcher/&quot;&gt;balenaEtcher&lt;/a&gt; to flash the Raspbian OS to your freshly formatted SD card.&lt;/li&gt;&lt;/ol&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Screenshot of the Disk Utility app on MacOS erasing the boot volume.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/2c6e387e920fe93631bdb7a137353ae083c7acb2-1844x1134.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/2c6e387e920fe93631bdb7a137353ae083c7acb2-1844x1134.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/2c6e387e920fe93631bdb7a137353ae083c7acb2-1844x1134.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/2c6e387e920fe93631bdb7a137353ae083c7acb2-1844x1134.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/2c6e387e920fe93631bdb7a137353ae083c7acb2-1844x1134.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1844 1844w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/2c6e387e920fe93631bdb7a137353ae083c7acb2-1844x1134.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;984&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Step two: format your SD card to MS-DOS (FAT) using Disk Utility&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of balenaEtcher flashing an SD card.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c967e130ef8a5905eba8dc6c3289de66f7129888-1596x958.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=399 399w, https://cdn.sanity.io/images/nkt6o869/production/c967e130ef8a5905eba8dc6c3289de66f7129888-1596x958.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=798 798w, https://cdn.sanity.io/images/nkt6o869/production/c967e130ef8a5905eba8dc6c3289de66f7129888-1596x958.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1197 1197w, https://cdn.sanity.io/images/nkt6o869/production/c967e130ef8a5905eba8dc6c3289de66f7129888-1596x958.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1596 1596w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c967e130ef8a5905eba8dc6c3289de66f7129888-1596x958.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1596&quot; width=&quot;1596&quot; height=&quot;958&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Step three: Use &lt;a href=&quot;https://www.balena.io/etcher/&quot;&gt;balenaEtcher&lt;/a&gt; to flash the Raspbian OS to your freshly formatted SD card.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;Setting up Raspberry Pi&lt;/h3&gt;&lt;p&gt;You’re halfway there! 🎉 Now that we have an SD card with the operating system we want to use, we need to make use of it with Raspberry Pi. Here’s where things get a little bit tricky.&lt;/p&gt;&lt;p&gt;There are two ways to set up Raspberry Pi:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Use an attached monitor, keyboard, and mouse.&lt;/li&gt;&lt;li&gt;Headless configuration via SSH or USB Console cable — the term “headless” refers to the lack of an attached monitor (the “head”), wherein the Pi can be controlled by another computer.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;If this is your first time tinkering with the Pi, I recommend the first option. Once you’ve inserted your SD card with Raspbian OS into the Pi, plug in your HDMI cable, keyboard, mouse, and power cable.&lt;/p&gt;&lt;p&gt;The Pi should now boot to the Raspbian desktop. Congrats! Now you can take some time to explore the desktop and see what it can do.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;The Raspbian desktop&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d9b85b4e9444a478e884e63ee0a6af0ffd5db9bc-640x400.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=320 320w, https://cdn.sanity.io/images/nkt6o869/production/d9b85b4e9444a478e884e63ee0a6af0ffd5db9bc-640x400.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=640 640w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d9b85b4e9444a478e884e63ee0a6af0ffd5db9bc-640x400.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=640&quot; width=&quot;640&quot; height=&quot;400&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p data-astro-cid-c6ccksbc&gt;The Raspbian desktop&lt;/p&gt; &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;Installing Swift&lt;/h3&gt;&lt;p&gt;To install Swift on our Pi, we need to connect it to the internet (either via ethernet or Wi-Fi, depending on the model of your Pi). Once you’re online, you’re ready to start the process of installing Swift.&lt;/p&gt;&lt;p&gt;There are 2 ways to install Swift. You can either &lt;a href=&quot;https://github.com/uraimo/buildSwiftOnARM#building-on-arm&quot;&gt;build Swift yourself&lt;/a&gt; or use &lt;a href=&quot;https://github.com/uraimo/buildSwiftOnARM#prebuilt-binaries&quot;&gt;pre-compiled binaries&lt;/a&gt;. I would highly recommend option 2 because the first option will take several days to do on a Raspberry PI. The precompiled binaries are possible because of the &lt;a href=&quot;https://swift-arm.com/&quot;&gt;Swift-ARM&lt;/a&gt; group. They have put together a &lt;a href=&quot;https://packagecloud.io/swift-arm/release&quot;&gt;repository&lt;/a&gt; that allows you to install Swift using the &lt;a href=&quot;https://help.ubuntu.com/lts/serverguide/apt.html&quot;&gt;&lt;em&gt;A&lt;/em&gt;dvanced &lt;em&gt;P&lt;/em&gt;ackage &lt;em&gt;T&lt;/em&gt;ool&lt;/a&gt; or &lt;code index=&quot;11&quot; isInline=&quot;true&quot;&gt;apt&lt;/code&gt; for short.&lt;/p&gt;&lt;p&gt;This command-line tool is kind of like the App Store for applications and packages on linux machines. We use &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;apt&lt;/code&gt; by typing &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;apt-get&lt;/code&gt; in the terminal, which is usually followed by a series of commands telling the tool what to do. In our case, we want to install Swift 5.0.2. For a list of all of the available Swift packages check &lt;a href=&quot;https://packagecloud.io/swift-arm/release&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;So, lets get started! Now that we know we want to install Swift via apt, lets add the swift-arm repo to our list of repositories.&lt;/p&gt;&lt;h4&gt;Add/Install the &lt;a href=&quot;https://swift-arm.com/&quot;&gt;swift-arm repo&lt;/a&gt; to our apt command-line tool.&lt;/h4&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Shell&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -s&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;https://packagecloud.io/install/repositories/swift-arm/release/script.deb.s&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;h&gt; | &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; bash&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the terminal performing the above command.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9bc5c93cd553c54ad28deb6063e7ffcf32356116-1140x566.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=285 285w, https://cdn.sanity.io/images/nkt6o869/production/9bc5c93cd553c54ad28deb6063e7ffcf32356116-1140x566.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=570 570w, https://cdn.sanity.io/images/nkt6o869/production/9bc5c93cd553c54ad28deb6063e7ffcf32356116-1140x566.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=855 855w, https://cdn.sanity.io/images/nkt6o869/production/9bc5c93cd553c54ad28deb6063e7ffcf32356116-1140x566.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1140 1140w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9bc5c93cd553c54ad28deb6063e7ffcf32356116-1140x566.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1140&quot; width=&quot;1140&quot; height=&quot;566&quot;/&gt;  &lt;/figure&gt; &lt;h4&gt;Use apt to install Swift from our freshly added repo from the line above.&lt;/h4&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Shell&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; apt-get&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; swift5=5.0.2-v0.4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the terminal performing the above command.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/fbba13294939e982c9eccb36c0121aea05716bd9-1144x544.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=286 286w, https://cdn.sanity.io/images/nkt6o869/production/fbba13294939e982c9eccb36c0121aea05716bd9-1144x544.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=572 572w, https://cdn.sanity.io/images/nkt6o869/production/fbba13294939e982c9eccb36c0121aea05716bd9-1144x544.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=858 858w, https://cdn.sanity.io/images/nkt6o869/production/fbba13294939e982c9eccb36c0121aea05716bd9-1144x544.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1144 1144w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/fbba13294939e982c9eccb36c0121aea05716bd9-1144x544.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1144&quot; width=&quot;1144&quot; height=&quot;544&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Thats it! You now have Swift installed on your Raspberry Pi.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A gif of Shaq wiggling his shoulders with the text &amp;#x27;Now that&amp;#x27;s what I&amp;#x27;m talking about!&amp;#x27;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/69081b896620738fe8822a937efe5ba44e12a540-354x324.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=177 177w, https://cdn.sanity.io/images/nkt6o869/production/69081b896620738fe8822a937efe5ba44e12a540-354x324.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=354 354w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/69081b896620738fe8822a937efe5ba44e12a540-354x324.gif?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=354&quot; width=&quot;354&quot; height=&quot;324&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Creating a test project&lt;/h3&gt;&lt;p&gt;At the moment, the &lt;a href=&quot;https://www.techrepublic.com/article/pro-tip-using-apples-swift-repl-from-the-terminal/&quot;&gt;Swift REPL&lt;/a&gt; is not operational — but everything else works. To give it a test run, let’s create a quick Swift package using Swift Package Manager.&lt;/p&gt;&lt;h4&gt;Create a new directory called MyFirstProject&lt;/h4&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Shell&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;mkdir&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; MyFirstProject&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the terminal performing the above command.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/dc79f049b74822baa534b2d9ea010aadf13bb5f9-1144x448.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=286 286w, https://cdn.sanity.io/images/nkt6o869/production/dc79f049b74822baa534b2d9ea010aadf13bb5f9-1144x448.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=572 572w, https://cdn.sanity.io/images/nkt6o869/production/dc79f049b74822baa534b2d9ea010aadf13bb5f9-1144x448.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=858 858w, https://cdn.sanity.io/images/nkt6o869/production/dc79f049b74822baa534b2d9ea010aadf13bb5f9-1144x448.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1144 1144w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/dc79f049b74822baa534b2d9ea010aadf13bb5f9-1144x448.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1144&quot; width=&quot;1144&quot; height=&quot;448&quot;/&gt;  &lt;/figure&gt; &lt;h4&gt;Change your working directory to be your newly created MyFirstProject directory&lt;/h4&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Shell&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; MyFirstProject&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the terminal performing the above command.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/315446fc5e055cb639082502fe386f941da7be08-1140x470.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=285 285w, https://cdn.sanity.io/images/nkt6o869/production/315446fc5e055cb639082502fe386f941da7be08-1140x470.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=570 570w, https://cdn.sanity.io/images/nkt6o869/production/315446fc5e055cb639082502fe386f941da7be08-1140x470.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=855 855w, https://cdn.sanity.io/images/nkt6o869/production/315446fc5e055cb639082502fe386f941da7be08-1140x470.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1140 1140w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/315446fc5e055cb639082502fe386f941da7be08-1140x470.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1140&quot; width=&quot;1140&quot; height=&quot;470&quot;/&gt;  &lt;/figure&gt; &lt;h4&gt;Create a new executable Swift Package&lt;/h4&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Shell&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;swift&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; package&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; init&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --type=executable&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the terminal performing the above command.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9dcd7177296933df2d513f9ed8229842a6c88f60-1138x544.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=285 285w, https://cdn.sanity.io/images/nkt6o869/production/9dcd7177296933df2d513f9ed8229842a6c88f60-1138x544.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=569 569w, https://cdn.sanity.io/images/nkt6o869/production/9dcd7177296933df2d513f9ed8229842a6c88f60-1138x544.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=854 854w, https://cdn.sanity.io/images/nkt6o869/production/9dcd7177296933df2d513f9ed8229842a6c88f60-1138x544.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1138 1138w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9dcd7177296933df2d513f9ed8229842a6c88f60-1138x544.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1138&quot; width=&quot;1138&quot; height=&quot;544&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Those three lines create an empty Swift package called &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;My​First​Project&lt;/code&gt;. To run this project, type &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;swift run&lt;/code&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the terminal performing the above command.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/efdf9c2a585f17289fff266866bdeb696897477f-1138x454.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=285 285w, https://cdn.sanity.io/images/nkt6o869/production/efdf9c2a585f17289fff266866bdeb696897477f-1138x454.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=569 569w, https://cdn.sanity.io/images/nkt6o869/production/efdf9c2a585f17289fff266866bdeb696897477f-1138x454.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=854 854w, https://cdn.sanity.io/images/nkt6o869/production/efdf9c2a585f17289fff266866bdeb696897477f-1138x454.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1138 1138w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/efdf9c2a585f17289fff266866bdeb696897477f-1138x454.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1138&quot; width=&quot;1138&quot; height=&quot;454&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Once your project has finished compiling, you should see &lt;em&gt;Hello, world!&lt;/em&gt; printed in the terminal.&lt;/p&gt;&lt;p&gt;Now that we’ve built our first program on the PI, lets make some small changes. From the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;My​First​Project&lt;/code&gt; directory, we will need to make a change in our &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;main.swift&lt;/code&gt; file. This is the code that is executed when we run our package via the command &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;swift run&lt;/code&gt;.&lt;/p&gt;&lt;h4&gt;Change your directory to the Sources/MyFirstProject directory&lt;/h4&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Shell&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; Sources/MyFirstProject&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the terminal performing the above command.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d50f9c9041a1ec28e3f11542c9b34ab0ff8cb3ad-1140x724.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=285 285w, https://cdn.sanity.io/images/nkt6o869/production/d50f9c9041a1ec28e3f11542c9b34ab0ff8cb3ad-1140x724.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=570 570w, https://cdn.sanity.io/images/nkt6o869/production/d50f9c9041a1ec28e3f11542c9b34ab0ff8cb3ad-1140x724.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=855 855w, https://cdn.sanity.io/images/nkt6o869/production/d50f9c9041a1ec28e3f11542c9b34ab0ff8cb3ad-1140x724.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1140 1140w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d50f9c9041a1ec28e3f11542c9b34ab0ff8cb3ad-1140x724.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1140&quot; width=&quot;1140&quot; height=&quot;724&quot;/&gt;  &lt;/figure&gt; &lt;h4&gt;Edit your main.swift file using the built-in &lt;a href=&quot;https://www.nano-editor.org/&quot;&gt;nano editor&lt;/a&gt;&lt;/h4&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Shell&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;nano&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; main.swift&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the terminal performing the above command.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/4ec1bb3cc946d0452d9276db4adf7ab7c33f167a-1136x552.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=284 284w, https://cdn.sanity.io/images/nkt6o869/production/4ec1bb3cc946d0452d9276db4adf7ab7c33f167a-1136x552.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=568 568w, https://cdn.sanity.io/images/nkt6o869/production/4ec1bb3cc946d0452d9276db4adf7ab7c33f167a-1136x552.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=852 852w, https://cdn.sanity.io/images/nkt6o869/production/4ec1bb3cc946d0452d9276db4adf7ab7c33f167a-1136x552.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1136 1136w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/4ec1bb3cc946d0452d9276db4adf7ab7c33f167a-1136x552.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1136&quot; width=&quot;1136&quot; height=&quot;552&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Once you’re in the nano editor, you’ll be able to edit your program to do as you please. Go ahead and replace the contents of your main.swift file with the following:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the terminal performing a print command.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/2c5bc2e5dc5772c9774041e07c3943838300812e-1140x552.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=285 285w, https://cdn.sanity.io/images/nkt6o869/production/2c5bc2e5dc5772c9774041e07c3943838300812e-1140x552.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=570 570w, https://cdn.sanity.io/images/nkt6o869/production/2c5bc2e5dc5772c9774041e07c3943838300812e-1140x552.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=855 855w, https://cdn.sanity.io/images/nkt6o869/production/2c5bc2e5dc5772c9774041e07c3943838300812e-1140x552.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1140 1140w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/2c5bc2e5dc5772c9774041e07c3943838300812e-1140x552.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1140&quot; width=&quot;1140&quot; height=&quot;552&quot;/&gt;  &lt;/figure&gt; &lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;main.swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Hello, Marc!&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Feel free to replace the name with your name. To save your changes perform the following:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Press &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;CTRL + X&lt;/code&gt; to save the file.&lt;/li&gt;&lt;li&gt;Confirm your changes by pressing the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Y&lt;/code&gt; key.&lt;/li&gt;&lt;li&gt;Confirm that you want to make to changes to the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;main.swift&lt;/code&gt; file by pressing the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Enter&lt;/code&gt; key.&lt;/li&gt;&lt;/ul&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the terminal performing the above command.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/2bfe64d70c58b97ae69e15c91db85fbb53812160-1140x554.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=285 285w, https://cdn.sanity.io/images/nkt6o869/production/2bfe64d70c58b97ae69e15c91db85fbb53812160-1140x554.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=570 570w, https://cdn.sanity.io/images/nkt6o869/production/2bfe64d70c58b97ae69e15c91db85fbb53812160-1140x554.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=855 855w, https://cdn.sanity.io/images/nkt6o869/production/2bfe64d70c58b97ae69e15c91db85fbb53812160-1140x554.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1140 1140w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/2bfe64d70c58b97ae69e15c91db85fbb53812160-1140x554.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1140&quot; width=&quot;1140&quot; height=&quot;554&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the terminal performing the above command.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/749435c44fac8260f18dfa3195bfdf1e9f0433c1-1136x556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=284 284w, https://cdn.sanity.io/images/nkt6o869/production/749435c44fac8260f18dfa3195bfdf1e9f0433c1-1136x556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=568 568w, https://cdn.sanity.io/images/nkt6o869/production/749435c44fac8260f18dfa3195bfdf1e9f0433c1-1136x556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=852 852w, https://cdn.sanity.io/images/nkt6o869/production/749435c44fac8260f18dfa3195bfdf1e9f0433c1-1136x556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1136 1136w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/749435c44fac8260f18dfa3195bfdf1e9f0433c1-1136x556.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1136&quot; width=&quot;1136&quot; height=&quot;556&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;We have now completed our changes and its time to re-run our program. Type the following into terminal window:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Shell&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;swift&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; run&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the terminal performing the above command.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c84a956fbb90ed54ccf2251ba96ae0a08cd6d8dd-1140x554.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=285 285w, https://cdn.sanity.io/images/nkt6o869/production/c84a956fbb90ed54ccf2251ba96ae0a08cd6d8dd-1140x554.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=570 570w, https://cdn.sanity.io/images/nkt6o869/production/c84a956fbb90ed54ccf2251ba96ae0a08cd6d8dd-1140x554.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=855 855w, https://cdn.sanity.io/images/nkt6o869/production/c84a956fbb90ed54ccf2251ba96ae0a08cd6d8dd-1140x554.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1140 1140w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c84a956fbb90ed54ccf2251ba96ae0a08cd6d8dd-1140x554.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1140&quot; width=&quot;1140&quot; height=&quot;554&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Congratulations! Once your code has compiled, the terminal should now display your recently modified string.&lt;/p&gt;&lt;p&gt;Now that Swift is installed, there’s so much more that you can do. To control hardware — like LEDs, motors, and relays — you can use the help of a library for hardware projects on Linux/ARM boards called &lt;a href=&quot;https://github.com/uraimo/SwiftyGPIO&quot;&gt;SwiftyGPIO&lt;/a&gt;. Have fun experimenting with Swift on the Raspberry Pi!&lt;/p&gt;&lt;p&gt;And if you enjoyed this blog post, you might also enjoy my &lt;a href=&quot;https://lickability.com/blog/swift-on-raspberry-pi-workshop/&quot;&gt;three-part series&lt;/a&gt; about using Swift on a Raspberry Pi to create a distance-measuring system for an autonomous car.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Interested in working with Swift on embedded devices? We‘ve got experience! &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Give us a ring&lt;/a&gt;.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Marc Aupont</author></item><item><title>Swift Playgrounds is for Everyone</title><link>https://lickability.com/blog/swift-playgrounds-is-for-everyone/</link><guid isPermaLink="true">https://lickability.com/blog/swift-playgrounds-is-for-everyone/</guid><description>You don&apos;t need to be an engineer to learn Swift</description><pubDate>Thu, 01 Aug 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Ever since joining Lickability as an Account Manager three months ago, my responsibilities have included finding and pursuing leads, working with potential clients through every stage of a project, drafting estimates and contracts, and sending lots and lots of emails. It’s my job to help clients figure out how we can meet their unique needs with the services we can provide.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/09196a97d2452fb0055ec8042835fd2c6d192c8a-2224x1668.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/09196a97d2452fb0055ec8042835fd2c6d192c8a-2224x1668.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/09196a97d2452fb0055ec8042835fd2c6d192c8a-2224x1668.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/09196a97d2452fb0055ec8042835fd2c6d192c8a-2224x1668.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/09196a97d2452fb0055ec8042835fd2c6d192c8a-2224x1668.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/09196a97d2452fb0055ec8042835fd2c6d192c8a-2224x1668.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2224 2224w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/09196a97d2452fb0055ec8042835fd2c6d192c8a-2224x1668.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;My final challenge in Learn To Code 2&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Although my background is in engineering — I studied mechanical engineering and engineering management — my education and the first four years of my career were outside of tech. Because of that, I’m somewhat of a novice when it comes to software development. For the past three months, I’ve been working on improving that so I can talk to our engineers and clients about app development. I learn best by doing, so I’ve been using Apple’s &lt;a href=&quot;https://www.apple.com/swift/playgrounds/&quot;&gt;Swift Playgrounds&lt;/a&gt; Learn to Code modules on an iPad here in the office to learn the fundamentals.&lt;/p&gt;&lt;p&gt;The Learn to Code series has three modules, accompanied by additional lessons, developed both by Apple and third parties, in the Swift Playgrounds app. Learn to Code 1 focuses on the fundamentals of coding and Swift, covering topics like commands, functions, bug fixing, loops, conditional code, and operators. Learn to Code 2 starts to get into the meat and potatoes of Swift with topics like variables, types, initialization, parameters, and arrays. I’m just scratching the surface of Learn to Code 3 now, but I can say that this module is really starting to feel like code that could be used in real world app development.&lt;/p&gt;&lt;p&gt;The lessons are presented as a series of puzzles that can be solved with code, with makes them fun to complete and great for any skill level. I sailed through most of the first module pretty easily (although I did run into some trouble with infinitely repeating while loops that I had to ask our engineers for help with 😅) and had a slightly tougher time with the second module. I started AirPlaying my code in our conference room so engineers could stop by and pair with me while I talked through my thought processes.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/26413aea110cd08cd1684985c349db40c6e48d5f-1668x1611.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/26413aea110cd08cd1684985c349db40c6e48d5f-1668x1611.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/26413aea110cd08cd1684985c349db40c6e48d5f-1668x1611.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/26413aea110cd08cd1684985c349db40c6e48d5f-1668x1611.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/26413aea110cd08cd1684985c349db40c6e48d5f-1668x1611.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1668 1668w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/26413aea110cd08cd1684985c349db40c6e48d5f-1668x1611.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1545&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;A screenshot of code in the Swift Playgrounds app&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;What I’ve learned&lt;/h3&gt;&lt;p&gt;Learning to code with Swift Playgrounds has several benefits that make it worth the effort for someone in a sales role at a software company. First, it gives me a better understanding of and appreciation for the work that our engineers do. This context makes it easier for me to evaluate potential clients — we want our engineers to work on interesting and fulfilling projects, and learning about programming has helped me seek out the best clients for us.&lt;/p&gt;&lt;p&gt;Second, I have a better understanding of clients’ needs, which can help me answer their questions and find solutions that best meet those needs without always having to consult with our engineers. Showing clients that I have a baseline knowledge about the services they need helps us establish trust early, and makes the rest of the sales process much smoother.&lt;/p&gt;&lt;p&gt;Finally, learning how to code is a rewarding and enjoyable experience! Solving puzzles and learning new things are some of the reasons I got into engineering in the first place, and diving into Swift has been a great way to continue to experience that. I’ve gotten a lot of personal and professional value out of Swift Playgrounds, not just because I’m learning a new skill, but because I’m learning about the process and best practices involved in coding. Here are some of the most important things I’ve learned from Swift Playgrounds that I use every day:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Pattern recognition:&lt;/strong&gt; I look for and automate processes that I find myself repeating a lot — this is especially useful when sending lots of emails. I use email templates for common messages I send, &lt;a href=&quot;https://calendly.com&quot;&gt;Calendly&lt;/a&gt; for scheduling client meetings, and document templates for project estimates and contracts.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Pair programming:&lt;/strong&gt; I’m more proactive about asking for help and working with my teammates on everything from writing up client documents to sending important emails.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Source control &amp;amp; documentation:&lt;/strong&gt; I make sure all client documents are up to date and filed correctly, document any changes I make to documents and flag them for review, and write down important workflow processes in &lt;a href=&quot;https://blog.lickability.com/switching-to-notion-51d7bcd2b94c&quot;&gt;Notion&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Iteration:&lt;/strong&gt; I recognize that things are always changing and there are always ways to improve my work, so I actively solicit feedback from teammates and clients to figure out what works and what doesn’t. After evaluating feedback, I develop solutions to address any problem areas in my workflow, and then continuously iterate over those solutions to improve more over time.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Getting started with Swift Playgrounds is as easy as grabbing an iPad, &lt;a href=&quot;https://apps.apple.com/us/app/swift-playgrounds/id908519492&quot;&gt;downloading the Swift Playgrounds app&lt;/a&gt;, and diving into the Learn To Code 1 module. If you have any questions about it, &lt;a href=&quot;https://twitter.com/ThomasDeVuono&quot;&gt;I’m just a tweet away&lt;/a&gt;!&lt;/p&gt; </content:encoded><author>Thomas DeVuono</author></item><item><title>Our Swift Best Practices</title><link>https://lickability.com/blog/our-swift-best-practices/</link><guid isPermaLink="true">https://lickability.com/blog/our-swift-best-practices/</guid><description>Lickability’s guide to writing better Swift</description><pubDate>Thu, 25 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Code is written for humans. I’m not the first to say that, and won’t be the last. What you write will eventually be compiled away into something unintelligible, so how you choose to write code isn’t for the computer’s benefit. It’s for yourself, both now and later. It’s for any people working on a team with you. And it’s for anyone that stumbles upon your code after you’re gone.&lt;/p&gt;&lt;p&gt;In order to understand our code better, Lickability employs consistent practices — and to do that, we have a defined structure and style for the way we write code. Today, we’re sharing our &lt;a href=&quot;https://github.com/Lickability/swift-best-practices&quot;&gt;&lt;strong&gt;best practices guide&lt;/strong&gt;&lt;/a&gt; with you. This guide contains our preferred way of writing code, both in terms of architecture and the way style is enforced (through &lt;a href=&quot;https://github.com/realm/SwiftLint&quot;&gt;SwiftLint&lt;/a&gt;). It is intended to be a living repository that will be updated as the Swift language and our experience evolves.&lt;/p&gt;&lt;p&gt;If you want to use this, great! If you want to fork it and make changes, go ahead. We won’t be accepting issues or pull requests at this time, but we hope that you’ll find our approach to writing software interesting—and if there are aspects that you’d love to chat about, &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;let us know&lt;/a&gt;!&lt;/p&gt; </content:encoded><author>Andrew Harrison</author></item><item><title>10 Years of Lickability</title><link>https://lickability.com/blog/10-years-of-lickability/</link><guid isPermaLink="true">https://lickability.com/blog/10-years-of-lickability/</guid><description>An indie app studio retrospective</description><pubDate>Mon, 22 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Wow! Time flies when you’re having fun and making apps. Our company turned 10 this year, so we figured it’d be good to look back over everything we’ve accomplished together over the last decade. Let’s take a stroll down memory lane, shall we?&lt;/p&gt;&lt;h3&gt;2009&lt;/h3&gt;&lt;blockquote&gt;Launching in stealth mode.&lt;br&gt;&lt;br&gt;— Lickability (@lickability) &lt;a href=&quot;https://twitter.com/lickability/status/1916562199?ref_src=twsrc%5Etfw&quot;&gt;May 25, 2009&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;In 2009, &lt;a href=&quot;https://twitter.com/bcapps&quot;&gt;Brian Capps&lt;/a&gt; and I were friends about to graduate high school together in Philadelphia. That year, I had gotten obsessed with the incredible indie apps that were being released on the year-old App Store, and decided I wanted to try my hand at making one. After convincing Brian to work with me, we started Lickability from my parents’ kitchen table in Sicklerville, NJ. We named the company after a &lt;a href=&quot;https://twitter.com/lickability/status/942565273269424130&quot;&gt;Steve Jobs quote&lt;/a&gt; that continues to inspire us to this day.&lt;/p&gt;&lt;p&gt;Our summer consisted of late-night iChat AV calls pair programming, watching &lt;a href=&quot;https://twitter.com/edog1203&quot;&gt;Evan Doll&lt;/a&gt; teach the first section of &lt;a href=&quot;https://web.stanford.edu/class/cs193p/cgi-bin/drupal/&quot;&gt;CS 193P&lt;/a&gt; at Stanford, and finishing a 0.9 version of our first app, Broadway. The app never (officially) got released, and if you’re curious why, we wrote the &lt;a href=&quot;https://lickability.tumblr.com/post/6310108928/lessons-from-broadway&quot;&gt;lessons we learned here&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;2010&lt;/h3&gt;&lt;blockquote&gt;Interested in beta testing a new iPhone app I‘m working on?&lt;br&gt;http://testflightapp.com/join/2a6c0e8823f64f0f581efce254fca680-NDQ/&lt;br&gt;&lt;br&gt;— mb Bischoff (@mb) &lt;a href=&quot;https://twitter.com/mb/status/7919481535860736?ref_src=twsrc%5Etfw&quot;&gt;November 25, 2010&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;Brian and I both went off to college — him to the University of Miami and me to New Jersey Institute of Technology, where I met the mysterious third Lickability Partner: &lt;a href=&quot;https://twitter.com/twig777&quot;&gt;Andrew Harrison&lt;/a&gt;, who has become our engineering lead. Andrew and I stayed up late in our sixth-floor dorm room working on an “unfinished idea” that &lt;a href=&quot;https://twitter.com/al3x&quot;&gt;Alex Payne&lt;/a&gt; had posted online some years earlier called &lt;a href=&quot;https://al3x.net/posts/2009/06/15/quotidian.html&quot;&gt;Quotidian&lt;/a&gt;. Once we had something that vaguely worked, we called Brian and showed him a demo. This prototype became one of our most well-known and successful apps of all time, &lt;a href=&quot;https://quotebookapp.com&quot;&gt;Quotebook&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Quotebook was an app you could use to store, sort, and share the quotes that matter to you. And we worked on it through most of 2010 to make sure it worked as well as it could for 1.0. Afternoons spent on importing and exporting code, building performant search, and following &lt;a href=&quot;https://www.raywenderlich.com/3115-uiview-tutorial-for-ios-how-to-make-a-custom-uiview-in-ios-5-a-5-star-rating-view&quot;&gt;Ray Wenderlich tutorials to create a rating view&lt;/a&gt; are the memories that stick with me almost a decade later.&lt;/p&gt;&lt;h3&gt;2011&lt;/h3&gt;&lt;blockquote&gt;“Real artists ship.” –Steve Jobs&lt;br&gt;&lt;br&gt;— Quotebook (@quotebookapp) &lt;a href=&quot;https://twitter.com/quotebookapp/status/40952705400844288?ref_src=twsrc%5Etfw&quot;&gt;February 25, 2011&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;We shipped! After tons of beta testing, we released Quotebook on the App Store! And people loved it. Pastors, parents, and book-lovers wrote to us in droves with their feedback and feature requests. Reviews went up on all of &lt;a href=&quot;https://minimalmac.com/post/4233527241/quotebook-a-notebook-for-your-quotes-on-iphone&quot;&gt;our&lt;/a&gt;&lt;a href=&quot;https://www.macstories.net/reviews/quotebook-save-your-favorite-quotes/&quot;&gt;favorite&lt;/a&gt;&lt;a href=&quot;https://brooksreview.net/2011/04/quick-takes-on-five-apps-7/&quot;&gt;websites&lt;/a&gt;, and the app got featured on the front page of the App Store as a Staff Pick. We quickly got to work on point releases and plans for a sequel.&lt;/p&gt;&lt;p&gt;And then: &lt;a href=&quot;https://genius.com/Title-of-show-original-cast-september-song-lyrics&quot;&gt;&lt;em&gt;The Gray Lady, baby&lt;/em&gt;&lt;/a&gt;! I was offered a full-time job on the iOS team at The New York Times and dropped out of college to take it. My mission from the beginning was for us to use what I learned there to make Lickability even better, and that’s exactly what we did.&lt;/p&gt;&lt;h3&gt;2012&lt;/h3&gt;&lt;blockquote&gt;Quotebook 2.0. &lt;a href=&quot;https://t.co/wIybzijj&quot;&gt;http://t.co/wIybzijj&lt;/a&gt;.&lt;br&gt;&lt;br&gt;iPad, iCloud, and so much more.&lt;br&gt;&lt;br&gt;— Quotebook (@quotebookapp) &lt;a href=&quot;https://twitter.com/quotebookapp/status/208056285759934464?ref_src=twsrc%5Etfw&quot;&gt;May 31, 2012&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;The iPad had been out for a few years and folks were clamoring for Quotebook on iPad. But we knew that in order to do it right, we needed it to be a Universal app, and quotes had to sync seamlessly across devices. Quotebook 2.0 shipped after many weekends spent testing and debugging Apple’s nascent (and, in hindsight, totally broken) iCloud Core Data syncing system. Many radars were filed.&lt;/p&gt;&lt;p&gt;Lickability needed a logo, so we commissioned &lt;a href=&quot;https://twitter.com/amahnke&quot;&gt;Aaron Mahnke&lt;/a&gt;, now the famous podcaster of the &lt;a href=&quot;https://www.lorepodcast.com&quot;&gt;&lt;em&gt;Lore&lt;/em&gt;&lt;/a&gt; universe, to design one. By this point, all three of us were living together in an apartment in Newark, NJ that we affectionally called Lickability HQ. Brian had moved up from Miami to join me at NYTimes by day and continue building the Lickability empire at night. This was also the year of our first WWDC, where I spent the 5-hour wait for the keynote in line with the hosts of &lt;a href=&quot;https://atp.fm&quot;&gt;ATP&lt;/a&gt;, which wouldn’t launch until the following year.&lt;/p&gt;&lt;h3&gt;2013&lt;/h3&gt;&lt;blockquote&gt;Velocity is the only speed reading app designed for iOS 7. Read faster, one word at a time.&lt;br&gt;&lt;br&gt;Download it now. &lt;a href=&quot;https://t.co/eX93eIcyqA&quot;&gt;https://t.co/eX93eIcyqA&lt;/a&gt;&lt;br&gt;&lt;br&gt;— Accelerator (@acceleratorapp) &lt;a href=&quot;https://twitter.com/acceleratorapp/status/380887733671309312?ref_src=twsrc%5Etfw&quot;&gt;September 20, 2013&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;On the product front, Brian and Andrew took an offhanded comment about an idea for a speed reading app I had and turned it into a functional app prototype. After working on the app for months, with the help of &lt;a href=&quot;https://twitter.com/marcelomarfil?lang=en&quot;&gt;Marcelo Marfil&lt;/a&gt; for design and &lt;a href=&quot;https://twitter.com/talosman?lang=en&quot;&gt;Talos Tsui&lt;/a&gt; at &lt;a href=&quot;https://iconfactory.com&quot;&gt;Iconfactory&lt;/a&gt; for the icon, we launched &lt;a href=&quot;https://acceleratorapp.com&quot;&gt;Velocity&lt;/a&gt; (now Acceleator), a speed reading app with support for Instapaper and Pocket. We also brought on &lt;a href=&quot;https://twitter.com/bryarlybishop&quot;&gt;Bryarly Bishop&lt;/a&gt; part-time to help us with customer support and worked with &lt;a href=&quot;https://twitter.com/grantjbutler&quot;&gt;Grant Butler&lt;/a&gt; on a prototype for a Reddit app we never shipped, called Antenna.&lt;/p&gt;&lt;p&gt;This was also the first year we took on clients (now a majority of our business). In 2013, we were lucky to work with the folks at &lt;a href=&quot;https://www.pocketbracket.com/march-mobile-madness-app&quot;&gt;Pocket Bracket&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Grouper_social_club&quot;&gt;Grouper&lt;/a&gt; to apply our Objective-C knowledge and make their apps look, feel, and function better. We also attended and spoke at more conferences, like &lt;a href=&quot;https://cingleton.com&quot;&gt;Çingleton&lt;/a&gt; in Montreal, &lt;a href=&quot;https://matthewbischoff.com/rtfm/&quot;&gt;SecondConf&lt;/a&gt;, and WWDC for a second time.&lt;/p&gt;&lt;h3&gt;2014&lt;/h3&gt;&lt;blockquote&gt;Quotebook is featured in Reference on the &lt;a href=&quot;https://twitter.com/AppStore?ref_src=twsrc%5Etfw&quot;&gt;@AppStore&lt;/a&gt;. Thanks . &lt;a href=&quot;https://t.co/1pAKvDrNCD&quot;&gt;pic.twitter.com/1pAKvDrNCD&lt;/a&gt;&lt;br&gt;&lt;br&gt;— Quotebook (@quotebookapp) &lt;a href=&quot;https://twitter.com/quotebookapp/status/508681653167095808?ref_src=twsrc%5Etfw&quot;&gt;September 7, 2014&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;We released Quotebook 3, a complete redesign and rewrite of the app, and people loved the new look. We continued to &lt;a href=&quot;https://www.objc.io/issues/10-syncing-data/icloud-core-data/&quot;&gt;write&lt;/a&gt; and &lt;a href=&quot;https://www.theverge.com/2013/3/26/4148628/why-doesnt-icloud-just-work&quot;&gt;give interviews about iCloud Core Data&lt;/a&gt; in the hopes that Apple would get its act together.&lt;/p&gt;&lt;p&gt;We also did lots of experimentation in 2014. We tried (and failed) to port Velocity to both Android and Windows phone and found out that we’re strongest when we work on platforms we use every day. We also rebranded Velocity to Accelerator after selling the domain to a startup, which allowed us to turn Lickability into more than a side-project. And Accelerator was the most&lt;a href=&quot;https://www.producthunt.com/posts/accelerator&quot;&gt; upvoted product of the year on ProductHunt&lt;/a&gt;! 😸&lt;/p&gt;&lt;h3&gt;2015&lt;/h3&gt;&lt;blockquote&gt;I am “Going Indie” today: &lt;a href=&quot;https://t.co/y4Z83gdpBe&quot;&gt;https://t.co/y4Z83gdpBe&lt;/a&gt;&lt;br&gt;&lt;br&gt;— Brian Capps (@bcapps) &lt;a href=&quot;https://twitter.com/bcapps/status/585457467305418752?ref_src=twsrc%5Etfw&quot;&gt;April 7, 2015&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;In 2015, we set aside some money and we took the biggest risk we’ve ever taken: g&lt;a href=&quot;https://blog.lickability.com/going-indie-87b750419242?gi=60cc36916b28&quot;&gt;oing indie&lt;/a&gt;. Brian quit his day job to focus on Lickability full time. And it worked! His first project was a Swift collaboration with &lt;a href=&quot;https://twitter.com/calebd&quot;&gt;Caleb Davenport&lt;/a&gt; to rewrite and re-release Bugshot as &lt;a href=&quot;https://blog.lickability.com/we-re-super-excited-to-announce-our-newest-app-pinpoint-is-available-now-for-free-on-the-app-a9bf11bff0b1&quot;&gt;Pinpoint&lt;/a&gt;, after we acquired the app from &lt;a href=&quot;https://twitter.com/marcoarment&quot;&gt;Marco Arment&lt;/a&gt;. That landed us a spot in &lt;a href=&quot;https://daringfireball.net/linked/2015/06/03/pinpoint&quot;&gt;Daring Fireball’s Linked List&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;We also took on Meetup as a client and figured out lawyers, accountants, invoicing, bookkeeping, and all that fun business-y stuff. I attended Layers in San Francisco and then I &lt;a href=&quot;https://matthewbischoff.com/barely-managing/&quot;&gt;talked about management&lt;/a&gt; at &lt;a href=&quot;https://cocoalove.org&quot;&gt;CocoaLove&lt;/a&gt; in Philly before also joining Brian as a full-time Lickability employee.&lt;/p&gt;&lt;h3&gt;2016&lt;/h3&gt;&lt;blockquote&gt;Presenting PinpointKit, our new framework for sending better feedback.&lt;a href=&quot;https://t.co/qb6FVLcQ0w&quot;&gt;https://t.co/qb6FVLcQ0w&lt;/a&gt;&lt;a href=&quot;https://t.co/GhD8dPxEhI&quot;&gt;pic.twitter.com/GhD8dPxEhI&lt;/a&gt;&lt;br&gt;&lt;br&gt;— Lickability (@lickability) &lt;a href=&quot;https://twitter.com/lickability/status/741368513479200768?ref_src=twsrc%5Etfw&quot;&gt;June 10, 2016&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;In 2016, we moved into our first office space and added two new team members: Andrew, and our first purely engineering hire, &lt;a href=&quot;https://blog.lickability.com/michael-liberatore-joins-lickability-dc5973afb28&quot;&gt;Michael Liberatore&lt;/a&gt;. We also released our first open-source project, &lt;a href=&quot;https://blog.lickability.com/pinpointkit-676b74e8a196&quot;&gt;PinpointKit&lt;/a&gt;, a framework for developers to collect feedback from their beta testers that’s now used in apps around the world. We also made the hard decision to &lt;a href=&quot;https://blog.lickability.com/the-end-of-quotebook-9e19b5653cc9&quot;&gt;shut down Quotebook&lt;/a&gt;. And finally, I &lt;a href=&quot;https://matthewbischoff.com/write-your-way-out/&quot;&gt;spoke at Release Notes&lt;/a&gt; in Chicago about how we use writing as a system for solving big and complex problems.&lt;/p&gt;&lt;p&gt;Two major client projects that we’re really proud of shipped in 2016. We helped &lt;a href=&quot;https://blog.lickability.com/making-meetup-e8197c8df436&quot;&gt;Meetup&lt;/a&gt; redesign and rewrite their entire iOS app from the ground up in Swift. And we helped build a brand new news app for a prestigious publication, &lt;a href=&quot;https://blog.lickability.com/helping-build-the-new-yorker-today-a30b2ab1fffa#.b8b7hgyrv&quot;&gt;The New Yorker Today&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;2017&lt;/h3&gt;&lt;blockquote&gt;🔱 We were thrilled to work with our friends at &lt;a href=&quot;https://twitter.com/TheAtlantic?ref_src=twsrc%5Etfw&quot;&gt;@TheAtlantic&lt;/a&gt; to build their new, redesigned iOS app, out today!&lt;a href=&quot;https://t.co/WTg5jMpcoC&quot;&gt;https://t.co/WTg5jMpcoC&lt;/a&gt;&lt;br&gt;&lt;br&gt;— Lickability (@lickability) &lt;a href=&quot;https://twitter.com/lickability/status/900392309069623296?ref_src=twsrc%5Etfw&quot;&gt;August 23, 2017&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;Our biggest launch in 2017 was a redesign and rewrite of an app for one of our favorite publications, &lt;a href=&quot;https://blog.lickability.com/a-new-look-for-the-atlantic-app-a6bae3931a5b&quot;&gt;The Atlantic&lt;/a&gt;. The app was &lt;a href=&quot;https://beautifulpixels.com/iphone/the-atlantic-app/&quot;&gt;featured on Beautiful Pixels&lt;/a&gt; and readers were impressed with the new look and feel and iPad support. We also added clients like &lt;a href=&quot;https://www.mimeo.com/news/mimeo-photos-now-integrated-with-photos-app-in-macos-high-sierra/&quot;&gt;Mimeo&lt;/a&gt;, &lt;a href=&quot;https://houseparty.com&quot;&gt;Houseparty&lt;/a&gt;, and &lt;a href=&quot;https://jet.com&quot;&gt;Jet&lt;/a&gt; to our roster.&lt;/p&gt;&lt;p&gt;Our team grew by two engineers, &lt;a href=&quot;https://twitter.com/grantjbutler&quot;&gt;Grant Butler&lt;/a&gt; (joining us full-time) and &lt;a href=&quot;https://twitter.com/cordavi&quot;&gt;Michael Amundsen&lt;/a&gt;, our first junior iOS engineer. After WWDC, we thought about what we could build with the new iOS 11 APIs and prototyped an app called Shelf. While we ultimately &lt;a href=&quot;https://blog.lickability.com/shelf-a-retrospective-f0b3a95359ae&quot;&gt;decided not to ship it&lt;/a&gt;, we all learned a lot building the first version.&lt;/p&gt;&lt;h3&gt;2018&lt;/h3&gt;&lt;blockquote&gt;🏠 We have a new office! Take a look inside the new Lickability HQ in our latest blog post.&lt;a href=&quot;https://t.co/SLxuQV646O&quot;&gt;https://t.co/SLxuQV646O&lt;/a&gt;&lt;br&gt;&lt;br&gt;— Lickability (@lickability) &lt;a href=&quot;https://twitter.com/lickability/status/1058452346857025537?ref_src=twsrc%5Etfw&quot;&gt;November 2, 2018&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;It was time. We had outgrown our coworking space and “minimalist” website. So we worked with our friends at &lt;a href=&quot;https://pickaxe.nyc&quot;&gt;Pickaxe&lt;/a&gt; to design a snazzy &lt;a href=&quot;https://blog.lickability.com/new-year-new-site-499c6cc5c58c&quot;&gt;new site&lt;/a&gt; and Megan Leet at &lt;a href=&quot;https://doitperf.com&quot;&gt;Perf&lt;/a&gt; to help us decorate our &lt;a href=&quot;https://blog.lickability.com/our-new-office-is-official-88fc37eb3ef0&quot;&gt;brand new office&lt;/a&gt; in Chelsea. Also, &lt;a href=&quot;https://twitter.com/jilliangmeehan&quot;&gt;Jillian Meehan&lt;/a&gt; joined to help us get our operations act together and &lt;a href=&quot;https://twitter.com/digimarktech&quot;&gt;Marc Aupont&lt;/a&gt; came aboard as our seventh engineer.&lt;/p&gt;&lt;p&gt;It was also a year of close collaborations with clients. We spent months working with &lt;a href=&quot;https://twitter.com/amberdiscko&quot;&gt;Amber Discko&lt;/a&gt; on their Kickstarted self-care app called &lt;a href=&quot;https://aloebud.com&quot;&gt;Aloe Bud&lt;/a&gt;. After a &lt;a href=&quot;https://blog.lickability.com/aloe-bud-self-care-app-5408bb9827f8&quot;&gt;successful launch&lt;/a&gt;, both Aloe Bud and Houseparty were &lt;a href=&quot;https://twitter.com/lickability/status/1027629176390668293&quot;&gt;featured on the App Store&lt;/a&gt; as App of the Day.&lt;/p&gt;&lt;h3&gt;2019&lt;/h3&gt;&lt;blockquote&gt;👋 We + &lt;a href=&quot;https://twitter.com/bcapps?ref_src=twsrc%5Etfw&quot;&gt;@bcapps&lt;/a&gt; will be in San Jose all week for &lt;a href=&quot;https://twitter.com/hashtag/WWDC19?src=hash&amp;ref_src=twsrc%5Etfw&quot;&gt;#WWDC19&lt;/a&gt;. Say hi if you see us! &lt;a href=&quot;https://t.co/I8CgeoapRk&quot;&gt;pic.twitter.com/I8CgeoapRk&lt;/a&gt;&lt;br&gt;&lt;br&gt;— Lickability (@lickability) &lt;a href=&quot;https://twitter.com/lickability/status/1135399668932681728?ref_src=twsrc%5Etfw&quot;&gt;June 3, 2019&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;This year is only halfway done, but so far we’ve brought on &lt;a href=&quot;https://twitter.com/AshliRankin18&quot;&gt;Ashli Rankin&lt;/a&gt; with the launch of our iOS Apprenticeship and &lt;a href=&quot;https://twitter.com/ThomasDeVuono&quot;&gt;Thomas DeVuono&lt;/a&gt;, our first Account Manager. Also, we &lt;a href=&quot;https://blog.lickability.com/we-were-on-tv-281910f4b3b6&quot;&gt;appeared in a five-minute television spot&lt;/a&gt; on &lt;em&gt;CBS’s Innovation Nation with Mo Rocca&lt;/em&gt;!&lt;/p&gt;&lt;p&gt;We continued to grow our studio, expand our &lt;a href=&quot;https://blog.lickability.com/conference-condensed-wwdc-2019-fa8b405dd7e5&quot;&gt;WWDC presence&lt;/a&gt;, and got started on a new app. 🤫 I got onstage at &lt;a href=&quot;https://blog.lickability.com/conference-condensed-nsnorth-2019-4fcdb8116ff5&quot;&gt;NSNorth in Montreal&lt;/a&gt; to talk about 10 of the lessons we learned over the last decade. And we’ve got plenty more in store for the next 6 months…&lt;/p&gt;&lt;h3&gt;Thank You for Everything 🙏&lt;/h3&gt;&lt;p&gt;We wouldn’t be where we are today without the help of so many people. We want to offer a heartfelt thanks to everyone that helped along the way.&lt;/p&gt;&lt;p&gt;Thank you to our team, families, testers, contractors, customers, and clients. And special thanks to Aaron Mahnke, Alex Cox, Alex Hardiman, Alex Payne, Allen Pike, Andrew Carroll, Annie Maguire, Ben Brooks, Ben Scheirman, Brian Murphy, Bryan Irace, Caleb Davenport, Carly Occhifinto, Casey Liss, Chris Eidof, Christina Warren, Christine Chan, Craig Hockenberry, Daniel Pasco, David Barnard, DJ Brinkerhoff, Federico Viticci, Fiona Spruill, Greg Mavronicolas, Guy English, Jason Brennan, Jeff Grossman, Jeremy Swinnen, Jessie Char, Joe Cieplinski, Joe Fiore, John August, John Gruber, John Voorhees, Jon Mitchell, Kate Sloan, Kathy Campbell, Kelly Capps, Ken Ackerson, Maja Henderson, Marco Arment, Mark Kawano, Matt Alexander, Megan Leet, Merlin Mann, Michael Jurewitz, Mohit Pandey, Myke Hurley, Patrick Gibson, Patrick Rhone, Paul Bruneau, Paul Rehkugler, Peter Vidani, Rachel Viniar, Ricky Mondello, Rob Rhyne, Rob Rix, Sam Soffes, Shawn Blanc, Sid O’Neill, Soroush Khanlou, Stephen Hackett, Steve Matthews, Tara Mann, and Zack Sultan.&lt;/p&gt; </content:encoded><author>mb bischoff</author></item><item><title>Conference Condensed: WWDC 2019</title><link>https://lickability.com/blog/conference-condensed-wwdc-2019/</link><guid isPermaLink="true">https://lickability.com/blog/conference-condensed-wwdc-2019/</guid><description>+ Layers and AltConf</description><pubDate>Thu, 13 Jun 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week, we sent five Lickability team members to San Jose for WWDC, Layers, and AltConf. This week, we’re giving you a rundown of some of our favorite moments — strap in.&lt;/p&gt;&lt;h3&gt;WWDC&lt;/h3&gt;&lt;p&gt;Although nobody from Lickability had tickets to WWDC this year, we all took time to sit down together and watch the videos online. We’re still making our way through them, but here are some of our favorites so far:&lt;/p&gt;&lt;h4&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/215/&quot;&gt;Advances in Collection View Layout&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;(WWDC 215)&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;In Advances in Collection View Layout, Apple introduced &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Compositional​Layout&lt;/code&gt; and &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;NS​Collection​View​Compositional​Layout&lt;/code&gt; to much fanfare. In 50 minutes, we learned just how easy it is to take our complicated custom collection view layout subclasses and replace them with a few declarations of the section, group, and item components that comprise the new layout types.&lt;/p&gt;&lt;p&gt;Shout out to &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Size&lt;/code&gt; and &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;NS​Collection​Layout​Dimension&lt;/code&gt;! No longer will we need complicated logic and math to calculate frames for layout attributes. These classes make it possible to specify item layouts relative to their container size by simply describing what you want upfront using &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;fractional​Height()&lt;/code&gt; and &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;fractional​Width()&lt;/code&gt;, and &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;absolute()&lt;/code&gt; when your designs call for a specific item size. It’s also amazingly simple to use visually distinct layouts for each section of your collection view, specify paging behaviors, and nest vertically and horizontally scrolling regions in a single collection view.&lt;/p&gt;&lt;p&gt;We can’t wait to delete all of our likely-inefficient &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Collection​View​Layout&lt;/code&gt; and &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;UI​Collection​Flow​Layout&lt;/code&gt; subclasses!&lt;/p&gt;&lt;h4&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/408/&quot;&gt;Adopting Swift Packages&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;(WWDC 408)&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;We’ve waited a long time, having to rely on open source tools like Cocoapods and Carthage, but that wait is finally over. Apple is finally bringing first class third-party dependency management to Xcode, with Swift Packages. The syntax for defining how a package behaves remains the same from previous years, so any third-party libraries that have already been defined that way will work starting today. With &lt;a href=&quot;https://github.blog/2019-06-03-github-package-registry-will-support-swift-packages/&quot;&gt;GitHub already signed on to provide support&lt;/a&gt;, we can’t wait until existing packages are updated to support the newly integrated Swift Package Manager.&lt;/p&gt;&lt;h4&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/723/&quot;&gt;Advances in Foundation&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;(WWDC 723)&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Advances in Foundation is like many of our favorite WWDC sessions in years past: it doesn’t completely change everything we know about app development by introducing a new framework, language, or technology. Rather, we’re introduced to a number of quality-of-life improvements to the things we use on a daily basis that make our jobs less prone to error or repetitive. This session was jam-packed with more than a dozen &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Foundation&lt;/code&gt; improvements that had us clapping along with the audience. To name a few highlights:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;add​Barrier​Block&lt;/code&gt; on &lt;code index=&quot;2&quot; isInline=&quot;true&quot;&gt;Operation​Queue&lt;/code&gt; 😯&lt;/li&gt;&lt;li&gt;Ordered collection diffing 🎉&lt;/li&gt;&lt;li&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;List​Formatter&lt;/code&gt; 🤩&lt;/li&gt;&lt;li&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Relative​Date​Time​Formatter&lt;/code&gt; 🤯 You’re gonna want to watch this short session at half speed to absorb everything!&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/239/&quot;&gt;Great Developer Habits&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;(WWDC 239)&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;This was an excellent talk from Apple going over the habits great developers practice when making applications. It resonated with us because, even though the habits they mentioned focused on best practices in development, they are relatable and easily applicable in any daily workflow. The talk focused on 8 key points:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Organize&lt;/strong&gt; — organize your work functionally, group related information, keep your work up to date, have a zero tolerance policy for those small errors so they don’t pile up&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Track&lt;/strong&gt; — use source control whenever you can and write detailed comments on your work&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Document&lt;/strong&gt; — your comments should not only explain what something is for but why it was created, document things that are reusable so you don’t waste time redoing work, be descriptive when naming files&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Test&lt;/strong&gt; — have your work checked as part of your regular practice, getting a second set of eyes on something is a great way to catch small mistakes&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Analyze&lt;/strong&gt; — try and think about all the different ways your work will be received, don’t just look at it from your perspective, be efficient&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Evaluate&lt;/strong&gt; — leverage the community’s knowledge if you are stuck or are less experienced, spend the time when reviewing other’s work, don’t just skim, ensure consistency&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Decouple&lt;/strong&gt; — scale your work across everything you do when possible, share what you know with the community&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Manage&lt;/strong&gt; — understand the tools you depend on, have plan for if they are no longer useable&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;AltConf&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A group of developers at AltConf&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/978739dc10fe11d568b20da2ec0de8a1ddb24bcb-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/978739dc10fe11d568b20da2ec0de8a1ddb24bcb-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/978739dc10fe11d568b20da2ec0de8a1ddb24bcb-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/978739dc10fe11d568b20da2ec0de8a1ddb24bcb-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/978739dc10fe11d568b20da2ec0de8a1ddb24bcb-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/978739dc10fe11d568b20da2ec0de8a1ddb24bcb-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2048 2048w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/978739dc10fe11d568b20da2ec0de8a1ddb24bcb-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Even if you don’t get the golden ticket to attend Apple’s conference, being in the same city as WWDC is such a fun time for anyone in the iOS community. It’s a week filled with goodies, exciting announcements, and friends.&lt;/p&gt;&lt;p&gt;A couple of us used our time in San Jose to attend &lt;a href=&quot;http://altconf.com/&quot;&gt;AltConf&lt;/a&gt; this year, which had really awesome talks, interesting workshops, and plenty of chances to mingle with some great folks. And, as a slightly bittersweet bonus, it was held in a hotel right next to the San Jose Convention Center that WWDC was in, which meant we were able to interact with quite a few developers who were trekking back and forth between the two conferences.&lt;/p&gt;&lt;p&gt;The talks and workshops we attended at AltConf were great — especially one workshop where we had a chance to play with the latest CoreML updates that allow you to update your models on device — but we really want to shout out the amazing experience of getting to hang out with friends and meet lots of new people. That’s always, hands down, our favorite part of the week. All in all, AltConf is a great conference, and one of our favorites every year.&lt;/p&gt;&lt;h3&gt;Layers&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A photo of mb, Jillian, and Brian wearing their Layers badges&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/1a9977854ab2ad2a51836c241f026e73cd448f89-1532x2050.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=383 383w, https://cdn.sanity.io/images/nkt6o869/production/1a9977854ab2ad2a51836c241f026e73cd448f89-1532x2050.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=766 766w, https://cdn.sanity.io/images/nkt6o869/production/1a9977854ab2ad2a51836c241f026e73cd448f89-1532x2050.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1149 1149w, https://cdn.sanity.io/images/nkt6o869/production/1a9977854ab2ad2a51836c241f026e73cd448f89-1532x2050.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1532 1532w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/1a9977854ab2ad2a51836c241f026e73cd448f89-1532x2050.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1532&quot; width=&quot;1532&quot; height=&quot;2050&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;This year, three of us went to &lt;a href=&quot;https://layers.is/&quot;&gt;Layers&lt;/a&gt;, a single-track design and tech conference with amazing snacks. Layers is incredibly well-run, and if you haven’t been, we highly recommend going. Here are a few highlights from this year:&lt;/p&gt;&lt;h4&gt;Kelli Anderson&lt;/h4&gt;&lt;p&gt;As we walked into the theater to see &lt;a href=&quot;https://kellianderson.com/blog/&quot;&gt;Kelli Anderson&lt;/a&gt;, we were each handed a piece of paper that looked like &lt;a href=&quot;http://www.kellianderson.com/flexagon-template.pdf&quot;&gt;this&lt;/a&gt;. Before she began her talk, Kelli instructed us to start folding the paper along all of the score lines. As we folded, we learned who Kelli is: a designer, a paper engineer, and just a generally very cool person. She made books like &lt;a href=&quot;https://kellianderson.com/books/thecamera.html&quot;&gt;This Book Is A Camera&lt;/a&gt;, and &lt;a href=&quot;https://kellianderson.com/books/planetarium.html&quot;&gt;This Book Is A Planetarium&lt;/a&gt;. She made &lt;a href=&quot;https://kellianderson.com/blog/2011/04/12/a-paper-record-player/&quot;&gt;a record player out of paper&lt;/a&gt; for her friends’ wedding invitations — yes, really. And she made an &lt;a href=&quot;http://www.kellianderson.com/books/calculator.html&quot;&gt;“existential calculator” volvelle wheel&lt;/a&gt;, a fun and creative way to solve the age-old question: “Should I take that job?”&lt;/p&gt;&lt;p&gt;By the time Kelli finished walking us through the joys of creating art with paper, we were done folding and ready for the next step of the talk: building our &lt;a href=&quot;https://www.instagram.com/p/BlVqonEB5c5/&quot;&gt;paper flexagons&lt;/a&gt;. She showed us how to take the flat pieces of paper we had all been given and turn them into something dynamic and fun in just a few minutes — an A+ Layers experience.&lt;/p&gt;&lt;h4&gt;Linzi Berry&lt;/h4&gt;&lt;p&gt;&lt;a href=&quot;https://twitter.com/taptodismiss&quot;&gt;Linzi Berry&lt;/a&gt;, who leads design systems at Lyft, gave an inspiring and useful talk on design systems at the company and how she and the team used them to encode inclusive design for people of all abilities. She covered why accessible design matters, and discussed some of the tools she used at Lyft to ensure contrast and sizing was as readable as possible for all users. Most importantly, Linzi imparted some organizational anecdotes and wisdom about how to talk to others at your company, even a large one like Lyft, to obtain support for unified design systems and accessible design (spoiler: it included selfie sticks!).&lt;/p&gt;&lt;p&gt;The talk left us energized about making design work for everyone and armed with the knowledge to start building more inclusive interfaces!&lt;/p&gt;&lt;h4&gt;May-Li Khoe&lt;/h4&gt;&lt;p&gt;What is there to say about &lt;a href=&quot;http://maylikhoe.com/&quot;&gt;May-Li Khoe&lt;/a&gt; that hasn’t already been said? As a designer, dancer, DJ, and polymath, May-Li has worked on everything from art project protests to multitouch interfaces at Apple.&lt;/p&gt;&lt;p&gt;Her talk at Layers focused on her mission in life and the thread that runs through all her recent projects: &lt;em&gt;joyfully subverting the status quo&lt;/em&gt;. May-Li is “from a lot of places” and part of her upbringing all over the world taught her to think of her life as a blank canvas, not a checklist or a linear graph.&lt;/p&gt;&lt;p&gt;With vivid examples from activism (&lt;a href=&quot;http://www.ktvu.com/news/community-organizes-2nd-annual-bbq-n-while-black-party-at-lake-merritt-in-oakland&quot;&gt;BBQ ‘N While Black&lt;/a&gt; and &lt;a href=&quot;http://futbolistas4lifefilm.com/&quot;&gt;Futbolistias for Life&lt;/a&gt;), art (&lt;a href=&quot;https://www.michelecarlson.com/new-page&quot;&gt;We Are Against the Wall&lt;/a&gt;), and technology (Photo Booth and her new app &lt;a href=&quot;http://scribbletogether.com/&quot;&gt;Scribble Together&lt;/a&gt;, with &lt;a href=&quot;http://bridgermaxwell.com/&quot;&gt;Bridger Maxwell&lt;/a&gt;), she reminded us that designers are people that visualize what could be, help bring other people along, and help them to see it with us. She taught us that designers have a responsibility to subvert broken politics, systems, and power structures. May Li invited all of us to “make some friends and start some shit.” And that’s exactly what we plan to do.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/2bbf8614761c5a6721568d22697fb447e2933e26-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=384 384w, https://cdn.sanity.io/images/nkt6o869/production/2bbf8614761c5a6721568d22697fb447e2933e26-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=769 769w, https://cdn.sanity.io/images/nkt6o869/production/2bbf8614761c5a6721568d22697fb447e2933e26-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1153 1153w, https://cdn.sanity.io/images/nkt6o869/production/2bbf8614761c5a6721568d22697fb447e2933e26-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1537 1537w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/2bbf8614761c5a6721568d22697fb447e2933e26-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1537&quot; width=&quot;1537&quot; height=&quot;2049&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/ba7fd5ab29068fd4077abaa749e325a837366a71-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=384 384w, https://cdn.sanity.io/images/nkt6o869/production/ba7fd5ab29068fd4077abaa749e325a837366a71-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=769 769w, https://cdn.sanity.io/images/nkt6o869/production/ba7fd5ab29068fd4077abaa749e325a837366a71-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1153 1153w, https://cdn.sanity.io/images/nkt6o869/production/ba7fd5ab29068fd4077abaa749e325a837366a71-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1537 1537w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/ba7fd5ab29068fd4077abaa749e325a837366a71-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1537&quot; width=&quot;1537&quot; height=&quot;2049&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/2971b79490bfcce2162b0493ec407b7c5b04a14d-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=384 384w, https://cdn.sanity.io/images/nkt6o869/production/2971b79490bfcce2162b0493ec407b7c5b04a14d-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=769 769w, https://cdn.sanity.io/images/nkt6o869/production/2971b79490bfcce2162b0493ec407b7c5b04a14d-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1153 1153w, https://cdn.sanity.io/images/nkt6o869/production/2971b79490bfcce2162b0493ec407b7c5b04a14d-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1537 1537w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/2971b79490bfcce2162b0493ec407b7c5b04a14d-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1537&quot; width=&quot;1537&quot; height=&quot;2049&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6e4f2f392e9b77d5092693cb9262827d6a97cc5d-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=384 384w, https://cdn.sanity.io/images/nkt6o869/production/6e4f2f392e9b77d5092693cb9262827d6a97cc5d-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=769 769w, https://cdn.sanity.io/images/nkt6o869/production/6e4f2f392e9b77d5092693cb9262827d6a97cc5d-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1153 1153w, https://cdn.sanity.io/images/nkt6o869/production/6e4f2f392e9b77d5092693cb9262827d6a97cc5d-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1537 1537w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6e4f2f392e9b77d5092693cb9262827d6a97cc5d-1537x2049.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1537&quot; width=&quot;1537&quot; height=&quot;2049&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;If we got a chance to say hi to you in San Jose, thanks for helping make our week a fun one! And if we didn’t see you this year, let’s catch up soon. We’ve still got &lt;a href=&quot;https://twitter.com/lickability/status/1135328556538404864&quot;&gt;plenty of pins&lt;/a&gt; to give out.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Jillian Meehan</author></item><item><title>Yell it into the #void</title><link>https://lickability.com/blog/yell-it-into-the-void/</link><guid isPermaLink="true">https://lickability.com/blog/yell-it-into-the-void/</guid><description>The one channel every Slack needs</description><pubDate>Thu, 23 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We like to try new things in the &lt;a href=&quot;https://blog.lickability.com/slack-is-for-friends-too-57ab3f9d9da0&quot;&gt;Lickability Slack&lt;/a&gt; from time to time, like inviting friends of the company to join public channels, or using an app called &lt;a href=&quot;https://www.heytaco.chat&quot;&gt;Hey Taco!&lt;/a&gt; to give teammates praise in the form of 🌮 emojis. Now, we’ve added a new channel to our Slack — welcome to &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;#void&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;What do you do with that post that doesn’t fit into any other Slack channel? &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;#void&lt;/code&gt;. What do you do with that thought that probably shouldn’t be a tweet? #void. What do you do with all of the stuff that doesn’t really require a response or acknowledgement from anyone else, the stuff that you just need to get out of your system? &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;#void&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Sometimes you just want to scream into the void — so we made a Slack channel specifically for that. We were introduced to the idea by a similar channel, &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;#bad-attitude&lt;/code&gt;, in the &lt;a href=&quot;https://2019.xoxofest.com/&quot;&gt;XOXO&lt;/a&gt; Slack. So while we definitely aren’t pioneers of the &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;#void&lt;/code&gt; channel, we are huge advocates of it.&lt;/p&gt;&lt;h3&gt;Why #void?&lt;/h3&gt;&lt;p&gt;Slack is for work, but when you’re spending an entire day using it, you need to be able to use it to escape from work, too. That’s why we invite friends to our public Slack channels like &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;#tv&lt;/code&gt; and &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;#music&lt;/code&gt; and &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;#food&lt;/code&gt;. That’s also why it’s helpful to have a space where people can get out their frustration, random thoughts, or memes in a way that isn’t distracting or disruptive to other team members.&lt;/p&gt;&lt;p&gt;Yelling (or, in this case, typing) into the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;#void&lt;/code&gt; is therapeutic, and often funny—and once you get those thoughts out, it’s easier to return to your work with a clear head. Of course, that can come with a downside: a channel meant for purging your thoughts can easily become a very negative or judgmental place, which we want to be mindful of (yes, &lt;a href=&quot;https://github.com/Lickability/code-of-conduct&quot;&gt;Code of Conduct&lt;/a&gt; rules still very much apply).&lt;/p&gt;&lt;h3&gt;Your turn!&lt;/h3&gt;&lt;p&gt;At the end of the day, our goal is to have a safe space for our team to get it all out. As it turns out, Slack can be that space. And it can be that space for you, too. Here’s how we suggest getting started:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Make a new Slack channel. You don’t &lt;em&gt;have&lt;/em&gt; to give it a name that suggests existential dread, but we recommend it.&lt;/li&gt;&lt;li&gt;Set the channel’s topic to make the purpose super clear to everyone. We went with: “Yell about it into the void. (We recommend you mute this channel)” — which brings us to the next step.&lt;/li&gt;&lt;li&gt;We strongly advise that you mute your &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;#void&lt;/code&gt; channel. Since the whole point is to allow people to get out their feelings in the least disruptive way possible, you probably don‘t want to be notified every time a teammate needs to do some frustrated keysmashing.&lt;/li&gt;&lt;li&gt;If you’re the kind of person who likes to star Slack channels to keep them at the top of your sidebar, go ahead and star this one! &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;#void&lt;/code&gt; is only helpful if you remember it‘s there.&lt;/li&gt;&lt;/ol&gt; </content:encoded><author>Jillian Meehan</author></item><item><title>Conference Condensed: NSNorth 2019</title><link>https://lickability.com/blog/conference-condensed-nsnorth-2019/</link><guid isPermaLink="true">https://lickability.com/blog/conference-condensed-nsnorth-2019/</guid><description>A look back at my weekend in Montréal</description><pubDate>Thu, 09 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Two weeks ago, I stepped on stage at the St. James Theatre in Montréal to deliver my talk, &lt;em&gt;Growing Pains&lt;/em&gt;, to the audience of &lt;a href=&quot;https://nsnorth.ca&quot;&gt;NSNorth&lt;/a&gt;. Before that, I had the opportunity to spend the weekend learning and socializing with a delightful group of independent iOS app designers and developers in this beautiful, old city.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;mb outside the conference, in front of a sign that says NSNorth 2019.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b0363ed2acd5887062a784da481a5b5ab93ff05c-1373x1740.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=343 343w, https://cdn.sanity.io/images/nkt6o869/production/b0363ed2acd5887062a784da481a5b5ab93ff05c-1373x1740.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=687 687w, https://cdn.sanity.io/images/nkt6o869/production/b0363ed2acd5887062a784da481a5b5ab93ff05c-1373x1740.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1030 1030w, https://cdn.sanity.io/images/nkt6o869/production/b0363ed2acd5887062a784da481a5b5ab93ff05c-1373x1740.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1373 1373w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b0363ed2acd5887062a784da481a5b5ab93ff05c-1373x1740.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1373&quot; width=&quot;1373&quot; height=&quot;1740&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;mb outside the conference. Photo by &lt;a href=&quot;https://twitter.com/sparklesloan&quot;&gt;Kate Sloan&lt;/a&gt;.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;NSNorth is the spiritual successor to &lt;a href=&quot;https://cingleton.com/&quot;&gt;Çingleton&lt;/a&gt;, the conference that led us to turn Lickability &lt;a href=&quot;https://blog.lickability.com/going-indie-87b750419242&quot;&gt;into a business&lt;/a&gt;. And just like Çingleton, it’s run by two lovely Canadians, &lt;a href=&quot;https://twitter.com/_danbyers&quot;&gt;Dan Byers&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/philippec&quot;&gt;Philippe Casgrain&lt;/a&gt;. The conference was a blast — here are some of my favorite moments.&lt;/p&gt;&lt;h3&gt;Friday: Happy Hour &amp;amp; Ken’s Keynote 🍻&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Attendees of NSNorth waiting for the next event&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/4cf5c3aa831357bb00de6d434861dd58cba30322-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/4cf5c3aa831357bb00de6d434861dd58cba30322-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/4cf5c3aa831357bb00de6d434861dd58cba30322-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/4cf5c3aa831357bb00de6d434861dd58cba30322-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/4cf5c3aa831357bb00de6d434861dd58cba30322-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/4cf5c3aa831357bb00de6d434861dd58cba30322-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2048 2048w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/4cf5c3aa831357bb00de6d434861dd58cba30322-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p data-astro-cid-c6ccksbc&gt;Attendees of NSNorth waiting for the next event&lt;/p&gt; &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Friday night kicked off with a happy hour to get to know all the new faces and catch up with old friends. We gathered for a keynote by one of my favorite speakers from past WWDCs, &lt;a href=&quot;https://twitter.com/kocienda&quot;&gt;Ken Kocienda&lt;/a&gt;. Ken, who built the keyboard on the original iPhone, talked in-depth about the “elements” and “molecules” that combined at Apple in the mid-aughts to give birth to the most successful consumer product of all time.&lt;/p&gt;&lt;p&gt;He gave me and the whole crowd lots to think about:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;em&gt;How do you build an engineering environment that allows for rapid feedback but doesn’t encourage interruption?&lt;/em&gt;&lt;/li&gt;&lt;li&gt;&lt;em&gt;How can engineers and designers collaborate to solve really thorny problems like building a multitouch keyboard with autocorrect?&lt;/em&gt;&lt;/li&gt;&lt;li&gt;&lt;em&gt;How do you decide what to build in the first place?&lt;/em&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I’m looking forward to reading Ken’s book &lt;a href=&quot;https://creativeselection.io/&quot;&gt;&lt;em&gt;Creative Selection&lt;/em&gt;&lt;/a&gt; to go deeper into his thoughts on how the original iOS team tackled these and other questions.&lt;/p&gt;&lt;h3&gt;Saturday: Talks, Tears, &amp;amp; Wheels 🎡&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Attendees of NSNorth lining up for La Grande Roue de Montréal&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f8fa14d5b83e2de060f26f530ebdcd50d752afda-1472x1498.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=368 368w, https://cdn.sanity.io/images/nkt6o869/production/f8fa14d5b83e2de060f26f530ebdcd50d752afda-1472x1498.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=736 736w, https://cdn.sanity.io/images/nkt6o869/production/f8fa14d5b83e2de060f26f530ebdcd50d752afda-1472x1498.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1104 1104w, https://cdn.sanity.io/images/nkt6o869/production/f8fa14d5b83e2de060f26f530ebdcd50d752afda-1472x1498.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1472 1472w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f8fa14d5b83e2de060f26f530ebdcd50d752afda-1472x1498.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1472&quot; width=&quot;1472&quot; height=&quot;1498&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Queuing up to ride La Grande Roue de Montréal&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Saturday, a rainy day in Montréal, was spent learning from some of the titans of the iOS software community. &lt;a href=&quot;https://twitter.com/jamesthomson&quot;&gt;James Thomson&lt;/a&gt; of PCalc fame kicked off the morning with an impeccable walkthrough of the history of software easter eggs. We heard from &lt;a href=&quot;https://twitter.com/alainakafkes&quot;&gt;Alaina Kafkes&lt;/a&gt; about how the Medium iOS team has prioritized and implemented accessibility features for their reading experience. Everyone’s favorite reverse-engineer &lt;a href=&quot;https://twitter.com/_inside&quot;&gt;Gui Rambo&lt;/a&gt; gave a great talk about practical advice for approaching privacy and security in iOS apps, reminding us to consider data protection, logging, API security, and user consent and control of data handling.&lt;/p&gt;&lt;p&gt;In the afternoon, I also saw my favorite presentation of the entire conference, &lt;em&gt;Small But Mighty&lt;/em&gt; by &lt;a href=&quot;https://twitter.com/hidrees&quot;&gt;Huda Idrees&lt;/a&gt;. Her energy was infectious as she described the social mission of her company and how small teams can solve big problems. She also reminded the audience that, as engineers and creators, “We need to be looking at falling in love with problems, instead of falling in love with solutions.”&lt;/p&gt;&lt;p&gt;At lunch, we took a break from learning to ride La Grande Roue de Montréal, a large Ferris wheel in the old port, and to get to know our fellow attendees better. Afterward, &lt;a href=&quot;https://twitter.com/dimsumthinking&quot;&gt;Daniel Steinberg&lt;/a&gt; delivered an emotional talk that had the entire audience in tears. He talk about the deaths of his wife and daughter, and how “the best move on the board” is often figuring out how to spend more time with those you love and finding the balance between work and the life you want to live. I’m getting choked up just writing about it.&lt;/p&gt;&lt;p&gt;We capped off the evening with a delicious banquet dinner. Good wine, new friends, and connections across the table led to an evening of board games that I sadly ducked out of early to rest up for my talk on Sunday.&lt;/p&gt;&lt;h3&gt;Sunday: Swift, Subscriptions, and Speech 🎙&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;mb onstage at NSNorth in front of a slide with the Lickability logo&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/5784e44b51affda7ea1d87fcf751e68c4b4ee66b-2048x1336.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/5784e44b51affda7ea1d87fcf751e68c4b4ee66b-2048x1336.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/5784e44b51affda7ea1d87fcf751e68c4b4ee66b-2048x1336.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/5784e44b51affda7ea1d87fcf751e68c4b4ee66b-2048x1336.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/5784e44b51affda7ea1d87fcf751e68c4b4ee66b-2048x1336.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/5784e44b51affda7ea1d87fcf751e68c4b4ee66b-2048x1336.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2048 2048w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/5784e44b51affda7ea1d87fcf751e68c4b4ee66b-2048x1336.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1044&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;mb mentioning Lickability in their talk. Photo by &lt;a href=&quot;https://twitter.com/sparklesloan&quot;&gt;Kate Sloan&lt;/a&gt;.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Most of my Sunday was spent pacing around mentally rehearsing my points, but I did catch &lt;a href=&quot;https://twitter.com/chriseidhof&quot;&gt;Chris Eidhof’s&lt;/a&gt; virtuosic talk on Type-Driven Development in which he live-coded for an audience of hundreds, flawlessly as usual. Continuing on the theme of accessibility, &lt;a href=&quot;https://twitter.com/_leenam&quot;&gt;Leena Mansour&lt;/a&gt; explained how she used her phone on VoiceOver exclusively for 7 days and taught us all about how cool and useful the VoiceOver Rotor can be for accessibility power-users. &lt;a href=&quot;https://twitter.com/reneritchie&quot;&gt;Rene Ritchie&lt;/a&gt; walked us through how Mobile Nations has used tools and a remote-first culture to shape their success with iMore and other media brands. And &lt;a href=&quot;https://twitter.com/ishabazz&quot;&gt;Ish ShaBazz&lt;/a&gt; shared his experience with subscriptions in his app Capsicum.&lt;/p&gt;&lt;p&gt;My talk, &lt;em&gt;Growing Pains&lt;/em&gt;, covered the things I’ve learned in the last 10 years building Lickability: the things that get harder as you scale and what you can do to stay sane as your team or company grows. I covered topics like how we found our own office space, how we hire engineers, and the mental health challenges of running a small business. I’m glad the talk resonated with many folks, including a few that came up to me after the conference was over, as I was exploring the city’s speakeasies, to tell me how helpful the they found the specifics I shared the tools and processes we have used to scale the business.&lt;/p&gt;&lt;p&gt;Finally, the conference ended with a closing keynote from &lt;a href=&quot;https://twitter.com/kateo&quot;&gt;Kate O’Neill&lt;/a&gt; that called us all to think more deeply about the humanity and ethical considerations of the products and algorithms we make. Kate’s also written a book on the topic, &lt;a href=&quot;https://www.amazon.com/Tech-Humanist-Technology-Better-Business-ebook/dp/B07GBHTX9K&quot;&gt;&lt;em&gt;Tech Humanist&lt;/em&gt;&lt;/a&gt;, that she generously gifted to every attendee.&lt;/p&gt;&lt;h3&gt;Au Revoir 👋&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Notre-Dame Basilica of Montreal.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/4cf26988ab4db8f28a0012f0c47213384bd68358-1536x1714.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=384 384w, https://cdn.sanity.io/images/nkt6o869/production/4cf26988ab4db8f28a0012f0c47213384bd68358-1536x1714.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=768 768w, https://cdn.sanity.io/images/nkt6o869/production/4cf26988ab4db8f28a0012f0c47213384bd68358-1536x1714.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1152 1152w, https://cdn.sanity.io/images/nkt6o869/production/4cf26988ab4db8f28a0012f0c47213384bd68358-1536x1714.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1536 1536w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/4cf26988ab4db8f28a0012f0c47213384bd68358-1536x1714.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1536&quot; width=&quot;1536&quot; height=&quot;1714&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Notre-Dame Basilica of Montreal. Photo by &lt;a href=&quot;https://twitter.com/sparklesloan&quot;&gt;Kate Sloan&lt;/a&gt;.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;NSNorth is on an indefinite hiatus, but it definitely went out with a bang. I’m so honored I got to attend and present at its 5th iteration. I hope Dan and Phil enjoy a well-deserved break from the hectic life of conference organizing and that someone starts up another Canadian iOS conference soon. I’ll definitely miss the maple syrup, the bagels, and the brilliant developers and designers that made my weekend in Montréal great. 🇨🇦&lt;/p&gt; </content:encoded><author>mb bischoff</author></item><item><title>How do you collaborate on email?</title><link>https://lickability.com/blog/how-do-you-collaborate-on-email/</link><guid isPermaLink="true">https://lickability.com/blog/how-do-you-collaborate-on-email/</guid><description>…when everyone at your company uses a different email app</description><pubDate>Thu, 25 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Let’s just get this out of the way: most of the Lickability team uses (and loves!) &lt;a href=&quot;https://sparkmailapp.com&quot;&gt;Spark&lt;/a&gt; for email. For collaboration, especially, it’s great. But that collaboration only works if everyone is using the same app, which isn’t the case in our office. Some people use Apple’s default Mail app, some people use standard Gmail, and some people — you know who you are, mb — have even given apps like &lt;a href=&quot;https://superhuman.com&quot;&gt;Superhuman&lt;/a&gt; a try. (Personally, I’m using &lt;a href=&quot;https://polymail.io&quot;&gt;Polymail&lt;/a&gt; right now, and I love it. Don’t @ me.)&lt;/p&gt;&lt;p&gt;So what do you do when your team wants to collaborate on email, but everyone is using a different email app?&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the macOS app Spark Mail showing email collaboration&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/7224b98662fe5f269a56cdfb864b7346a73d83c9-1190x860.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=298 298w, https://cdn.sanity.io/images/nkt6o869/production/7224b98662fe5f269a56cdfb864b7346a73d83c9-1190x860.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=595 595w, https://cdn.sanity.io/images/nkt6o869/production/7224b98662fe5f269a56cdfb864b7346a73d83c9-1190x860.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=893 893w, https://cdn.sanity.io/images/nkt6o869/production/7224b98662fe5f269a56cdfb864b7346a73d83c9-1190x860.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1190 1190w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/7224b98662fe5f269a56cdfb864b7346a73d83c9-1190x860.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1190&quot; width=&quot;1190&quot; height=&quot;860&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Email collaboration with Spark&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;Why collaborate?&lt;/h3&gt;&lt;p&gt;Let’s say you have to send an important email to a client — before you slap your signature on it and hit the “send” button, you probably want to have at least one other pair of eyes on it. Maybe there are typos you missed, maybe a sentence or two could be worded better, or maybe you just need a quick “Looks good to me” before you feel okay sending it.&lt;/p&gt;&lt;p&gt;Clear and quality communication is important to us, which is why almost every email we send is done so collaboratively. Sometimes that means using an app like Spark to comment on and draft emails together, sometimes it means pasting an email draft into Slack and asking if it makes sense, and sometimes it just means saying out loud to each other, “I’m going to send this, is that okay?”&lt;/p&gt;&lt;p&gt;There are pros and cons to this, of course. Collaboration often results in sending cleaner, clearer, and more effective emails, and makes the email process more transparent. Team members feel more &lt;em&gt;in the loop&lt;/em&gt; when they help write an email, rather than seeing it for the first time when they’re CC’d. That said, email collaboration can eat up unnecessary time — think about how many minutes are spent asking people to look over emails before you send them when it would be quicker to just type something up and send it yourself. And in our case, just figuring out the best way to collaborate can eat up even &lt;em&gt;more&lt;/em&gt; unnecessary time.&lt;/p&gt;&lt;p&gt;At the end of the day, collaboration is essential — as long as you’re doing it right. So what’s the right way to do it?&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Two screenshots of the sharing feature in the Polymail iOS app&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/ab4395b8590da1f7b56a712887e5446ff2e65593-2801x3032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/ab4395b8590da1f7b56a712887e5446ff2e65593-2801x3032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/ab4395b8590da1f7b56a712887e5446ff2e65593-2801x3032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/ab4395b8590da1f7b56a712887e5446ff2e65593-2801x3032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/ab4395b8590da1f7b56a712887e5446ff2e65593-2801x3032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/ab4395b8590da1f7b56a712887e5446ff2e65593-2801x3032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/ab4395b8590da1f7b56a712887e5446ff2e65593-2801x3032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/ab4395b8590da1f7b56a712887e5446ff2e65593-2801x3032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2801 2801w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/ab4395b8590da1f7b56a712887e5446ff2e65593-2801x3032.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1732&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Sharing emails with Polymail&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;What we’ve tried&lt;/h3&gt;&lt;p&gt;We’ve yet to find the perfect solution for email collaboration. &lt;a href=&quot;https://sparkmailapp.com/teams&quot;&gt;Spark for Teams&lt;/a&gt; is great, but only works if everyone on your team is using Spark. &lt;a href=&quot;https://polymail.io/&quot;&gt;Polymail&lt;/a&gt; has a similar feature, and also makes it easy to create web links to emails so you can collaborate outside of the app — but, again, this is only useful if your whole team uses Polymail.&lt;/p&gt;&lt;p&gt;In our &lt;a href=&quot;https://blog.lickability.com/switching-to-notion-51d7bcd2b94c&quot;&gt;Switching to Notion&lt;/a&gt; blog post from a few months ago, we briefly talked about our “Email Scratchpad”: a Notion page exclusively for drafting emails together. Since then, we’ve stopped using it — it’s just not fast or convenient enough to be worth it. Copying and pasting the body of an email into Slack and editing a quick reply is quick and easy enough, so that’s often what we default to these days. But it doesn’t feel like a perfect solution.&lt;/p&gt;&lt;p&gt;So we’re asking you: how do you collaborate on email with your team? Let us know on Twitter — we want to hear from you!&lt;/p&gt; </content:encoded><author>Jillian Meehan</author></item><item><title>We Were On TV!</title><link>https://lickability.com/blog/we-were-on-tv/</link><guid isPermaLink="true">https://lickability.com/blog/we-were-on-tv/</guid><description>Check out Lickability on Innovation Nation</description><pubDate>Thu, 11 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Back in December, we got an email from CBS about &lt;a href=&quot;https://www.cbs.com/shows/innovation_nation/&quot;&gt;&lt;em&gt;The Henry Ford’s Innovation Nation with Mo Rocca&lt;/em&gt;&lt;/a&gt;, a weekly Saturday morning show that covers all kinds of innovators and change-makers. They told us they had been researching our company and thought our app &lt;a href=&quot;https://acceleratorapp.com&quot;&gt;Accelerator&lt;/a&gt; would be a great fit for a segment. 🥳&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c8c5fd5bfd50a16db724a26cd9bd2fc0bab5e568-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/c8c5fd5bfd50a16db724a26cd9bd2fc0bab5e568-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/c8c5fd5bfd50a16db724a26cd9bd2fc0bab5e568-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/c8c5fd5bfd50a16db724a26cd9bd2fc0bab5e568-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/c8c5fd5bfd50a16db724a26cd9bd2fc0bab5e568-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/c8c5fd5bfd50a16db724a26cd9bd2fc0bab5e568-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2048 2048w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c8c5fd5bfd50a16db724a26cd9bd2fc0bab5e568-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d55a113d88b8f7ff7b9bc53212d51a27c68ca000-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/d55a113d88b8f7ff7b9bc53212d51a27c68ca000-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/d55a113d88b8f7ff7b9bc53212d51a27c68ca000-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/d55a113d88b8f7ff7b9bc53212d51a27c68ca000-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/d55a113d88b8f7ff7b9bc53212d51a27c68ca000-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/d55a113d88b8f7ff7b9bc53212d51a27c68ca000-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2048 2048w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d55a113d88b8f7ff7b9bc53212d51a27c68ca000-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;A day of filming at Lickability HQ&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Weeks later, the CBS crew showed up at our office for a day of filming our spot for &lt;em&gt;Innovation Nation&lt;/em&gt;, which just aired last Saturday morning. mb, Brian, and Twig talked to the cohost Alie Ward all about the technology behind Accelerator and how it went from an idea they had back in college to an app that Lickability continues developing today—alongside all of the other &lt;a href=&quot;https://lickability.com/about&quot;&gt;work&lt;/a&gt; we do.&lt;/p&gt;&lt;blockquote&gt;It’s Epic &lt;a href=&quot;https://t.co/QYIkfOfsjq&quot;&gt;pic.twitter.com/QYIkfOfsjq&lt;/a&gt;&lt;br&gt;&lt;br&gt;— mb Bischoff (@mb) &lt;a href=&quot;https://twitter.com/mb/status/1114528024089772032?ref_src=twsrc%5Etfw&quot;&gt;April 6, 2019&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;We got up bright and early on Saturday morning to gather in the office with our friends and family and watch the show together — complete with waffles, coffee, and mimosas, of course. We want to say a big thanks to everyone who watched the show (thanks, Mom!) and to the lovely team at &lt;em&gt;Innovation Nation&lt;/em&gt; for their support of Accelerator!&lt;/p&gt;&lt;p&gt;If you missed the episode, you can watch it right &lt;a href=&quot;https://www.youtube.com/watch?v=dC11zzr751k&quot;&gt;here&lt;/a&gt;.&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Podcasts We Can’t Live Without</title><link>https://lickability.com/blog/podcasts-we-can-t-live-without/</link><guid isPermaLink="true">https://lickability.com/blog/podcasts-we-can-t-live-without/</guid><description>Brought to you by yet another mattress startup</description><pubDate>Thu, 21 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It’s probably happened to you: you’re out at a party with a few of your friends, and all of a sudden, you’re comparing podcast subscriptions. (No? Just us? Okay.) We’ve shared and critiqued each other’s podcast-listening choices plenty of times in the office, so we figured it was about time to extend that courtesy to you. Read on to find out what some of our favorite podcasts are and why we love them so much.&lt;/p&gt;&lt;h3&gt;Cartridge&lt;/h3&gt;&lt;p&gt;Brought to you by our very own Andrew Harrison and Michael Liberatore, &lt;a href=&quot;https://cartridge.simplecast.fm&quot;&gt;Cartridge&lt;/a&gt; is the only podcast about video games you’ll ever need (okay, we’re biased).&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why we love it:&lt;/strong&gt; It’s about video games! And we like video games!&lt;/p&gt;&lt;h3&gt;99% Invisible&lt;/h3&gt;&lt;p&gt;If you’re someone who loves to notice all the tiny details that go unnoticed, &lt;a href=&quot;https://99percentinvisible.org&quot;&gt;this podcast&lt;/a&gt; is for you. Roman Mars’ voice + really in-depth narrative storytelling about design and architecture = magic.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why we love it:&lt;/strong&gt; It keeps us looking up and paying attention to the details.&lt;/p&gt;&lt;h3&gt;Cortex&lt;/h3&gt;&lt;p&gt;Hosted by CGP Grey and Myke Hurley, &lt;a href=&quot;https://www.relay.fm/cortex&quot;&gt;Cortex&lt;/a&gt; is what happens when two nerds who know they should probably get back to work decide instead to spend two hours talking about time-tracking and old business books. And we &lt;em&gt;love &lt;/em&gt;it.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why we love it:&lt;/strong&gt; Productivity porn. Enough said.&lt;/p&gt;&lt;h3&gt;Do By Friday&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;http://dobyfriday.com&quot;&gt;Do By Friday&lt;/a&gt; is a podcast where our pals (and yours) Merlin Mann, Alex Cox, and Max Temkin challenge themselves and their friends every week. Challenges have ranged from “Solve a mystery,” to “Make bacon,” to “Wash your hands.” Hilarity and politics ensue.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why we love it:&lt;/strong&gt; It is almost impossible to get through an episode without laughing.&lt;/p&gt;&lt;h3&gt;Friendshipping!&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://friendshipping.simplecast.fm&quot;&gt;Jenn and Trin&lt;/a&gt; are here to solve all of the weird, icky friendship problems that we don’t know how to deal with. They give real (but not harsh!) advice for friendship-related questions sent in by listeners — and they do it beautifully.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why we love it:&lt;/strong&gt; It manages to make dealing with even the trickiest friendship troubles fun.&lt;/p&gt;&lt;h3&gt;Swift Over Coffee&lt;/h3&gt;&lt;p&gt;What’s not to like about two guys having a friendly chat about Swift? Paul Hudson and Sean Allen cover all kinds of Swift news on &lt;a href=&quot;https://anchor.fm/swiftovercoffee&quot;&gt;this podcast&lt;/a&gt;, as well as topics suggested by the iOS community via open ballot.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why we love it:&lt;/strong&gt; It’s about a topic very close to our hearts, and we genuinely appreciate Paul and Sean’s work in the Swift community.&lt;/p&gt;&lt;h3&gt;How I Built This with Guy Raz&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://www.npr.org/podcasts/510313/how-i-built-this&quot;&gt;How I Built This&lt;/a&gt; is the ultimate &lt;em&gt;inspiration &lt;/em&gt;podcast. Hosted by Guy Raz, this podcast about all kinds of innovators — basically, it’s about people who are doing cool things and how they’re making it happen. If you have an interest in building things, this podcast is the motivation you need to follow through.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why we love it:&lt;/strong&gt; We love learning about how others have found success and the journeys that led them to it.&lt;/p&gt;&lt;h3&gt;Watch Out for Fireballs!&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://www.watchoutforfireballs.com&quot;&gt;Watch Out for Fireballs!&lt;/a&gt; is a game club (think &lt;em&gt;book club&lt;/em&gt;, but for video games) podcast on which Gary Butterfield and Kole Ross discuss a single game at length in each episode.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why we love it:&lt;/strong&gt; After covering generalities, mechanics, and impressions, the hosts talk about each and every area or level of the game chronologically, making it a joy to play along while listening.&lt;/p&gt;&lt;h3&gt;Bonfireside Chat&lt;/h3&gt;&lt;p&gt;In &lt;a href=&quot;https://www.bonfireside.chat&quot;&gt;Bonfireside Chat&lt;/a&gt;, Gary and Kole of Watch Out for Fireballs! dive even deeper into games like Dark Souls (one of our favorites) with entire seasons devoted to specific games.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why we love it:&lt;/strong&gt; It’s everything we love about Watch Out for Fireballs, but way more in-depth. Both shows are evergreen.&lt;/p&gt;&lt;h3&gt;Call Your Girlfriend&lt;/h3&gt;&lt;p&gt;Everyone has long-distance besties, but not everyone is cool enough to make a &lt;a href=&quot;https://www.callyourgirlfriend.com&quot;&gt;podcast&lt;/a&gt; with them. Ann Friedman and Aminatou Sow call each other every week to talk about politics, pop culture, and how they do what they do.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why we love it:&lt;/strong&gt; This duo is smart, inspiring, and just a delight to listen to.&lt;/p&gt;&lt;h3&gt;Honorable mentions&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;http://5by5.tv/b2w/&quot;&gt;Back to Work&lt;/a&gt; ∙ &lt;a href=&quot;https://www.blacksintechnology.net&quot;&gt;Blacks in Technology&lt;/a&gt; ∙ &lt;a href=&quot;https://itunes.apple.com/us/podcast/ios-dev-discussions-sean-allen/id1426167395?mt=2&quot;&gt;iOS Dev Discussions&lt;/a&gt; ∙ &lt;a href=&quot;https://www.moneyguy.com&quot;&gt;The Money Guy Show&lt;/a&gt; ∙ &lt;a href=&quot;https://www.relay.fm/radar&quot;&gt;Under The Radar&lt;/a&gt; ∙ &lt;a href=&quot;https://soundcloud.com/thesuitepodcast&quot;&gt;The Suite Podcast&lt;/a&gt; ∙ &lt;a href=&quot;https://stacktracepodcast.fm&quot;&gt;StackTrace&lt;/a&gt; ∙ &lt;a href=&quot;https://www.relay.fm/rd&quot;&gt;Reconcilable Differences&lt;/a&gt; ∙ &lt;a href=&quot;https://www.relay.fm/rocket&quot;&gt;Rocket&lt;/a&gt; ∙ &lt;a href=&quot;http://www.merlinmann.com/roderick/&quot;&gt;Roderick on the Line&lt;/a&gt; ∙ &lt;a href=&quot;https://www.gimletmedia.com/reply-all&quot;&gt;Reply All&lt;/a&gt; ∙ &lt;a href=&quot;http://videogameshotdog.com&quot;&gt;Video Games Hot Dog&lt;/a&gt; ∙ &lt;a href=&quot;https://glitch.com/culture/function/&quot;&gt;Function with Anil Dash&lt;/a&gt; ∙ &lt;a href=&quot;https://www.theverge.com/the-vergecast&quot;&gt;The Vergecast&lt;/a&gt; ∙ &lt;a href=&quot;https://www.gimletmedia.com/the-cut-on-tuesdays&quot;&gt;The Cut on Tuesdays&lt;/a&gt; ∙ &lt;a href=&quot;https://www.whoweekly.us&quot;&gt;Who? Weekly&lt;/a&gt; ∙ &lt;a href=&quot;https://www.swiftbysundell.com/podcast&quot;&gt;Swift by Sundell&lt;/a&gt; ∙ &lt;a href=&quot;https://releasenotes.tv&quot;&gt;Release Notes&lt;/a&gt; ∙ &lt;a href=&quot;https://punchupthejam.com&quot;&gt;Punch Up The Jam&lt;/a&gt;&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Growing our Small Business</title><link>https://lickability.com/blog/growing-our-small-business/</link><guid isPermaLink="true">https://lickability.com/blog/growing-our-small-business/</guid><description>A look into our hiring process</description><pubDate>Thu, 14 Feb 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In case you haven’t noticed, &lt;a href=&quot;http://jobs.lickability.com/&quot;&gt;we’re hiring&lt;/a&gt;. Expanding a small team like ours can be a weird, tricky process, so we thought we’d share a little bit about how we do it.&lt;/p&gt;&lt;h3&gt;When we hire&lt;/h3&gt;&lt;p&gt;We consider ourselves risk-conscious at Lickability, which means that we tend to be fairly conservative about hiring. We hire new employees based on an existing or immediate upcoming need, rather than trying to anticipate too far into the future. Because of that, we tend not to take on a lot of projects, which means we are far less likely to be caught in a position where we’ve just hired someone and don’t have anything to work on. It’s important to us that new employees feel like they have an important role at the company from the start, and the way we approach hiring helps us accomplish that.&lt;/p&gt;&lt;p&gt;So, how do we decide it’s time to hire someone new? The short answer is that it depends on how much work we have. We look at our current list of clients, our contracts, and any existing leads that we have. If we have more upcoming projects than we have engineers, it’s time to consider bringing on a new team member.&lt;/p&gt;&lt;h3&gt;Who we hire&lt;/h3&gt;&lt;p&gt;Most of the time, the new employees we hire are Swift engineers. But because Lickability is a small, relatively young company, it’s not uncommon for us to realize we need to hire someone to fill a totally new role.&lt;/p&gt;&lt;p&gt;To decide what positions we need to hire for, we take a look at the responsibilities that we’re either lacking or currently taking on ourselves. Then we try to shape a role around some, or all, of those responsibilities. A big part of writing a job description for a brand new role is looking at companies we admire and compete with that have the same position. Once we’ve read and talked to folks about how &lt;em&gt;they&lt;/em&gt;structured a role, we can come up with something that’s right for us.&lt;/p&gt;&lt;p&gt;Keeping in mind that the person who fits our specific needs has to actually exist, we try to limit requirements (like education and years of experience) to only what’s truly necessary. Having job requirements that aren’t &lt;em&gt;actually&lt;/em&gt; requirements tends to &lt;a href=&quot;https://hbr.org/2014/08/why-women-dont-apply-for-jobs-unless-theyre-100-qualified&quot;&gt;systematically discourage&lt;/a&gt; women and other underrepresented groups from applying, and that’s the last thing we want. Being honest and clear about what we actually need — and being willing to compromise and change our job description if the applicants we see fit a slightly different profile better — is key when it comes to hiring for positions we’re less familiar with.&lt;/p&gt;&lt;h3&gt;How we hire&lt;/h3&gt;&lt;p&gt;Our hiring process has three steps: a phone screen, a take-home project, and an interview. The phone screen is a quick conversation to help us get a basic understanding of a person’s skills and personality. After a candidate passes the phone screen, we send them a small project to complete, relevant to the skills we’re looking for, that helps us get an understanding of what they consider to be their best work. Then, finally, the in-person interview is when we ask the candidate questions about the code test, their technical knowledge, and what would make them a good fit for our team.&lt;/p&gt;&lt;p&gt;We adhere fairly strictly to this process, and use the same take-home project and set of interview questions for everyone, which allows us to create a baseline for comparing applicants. We don’t believe in timed coding or algorithmic questions, instead striving to create an environment in our interviews that feels much closer to what it would be like if they were actually working here. For example, applicants can use their laptops to look up documentation and Google answers to questions if they need to, and they’re encouraged to ask questions and generate discussion during the interview.&lt;/p&gt;&lt;p&gt;The nice thing about hiring is that we have the chance to refine the process every time we do it, and if there’s one thing we love at Lickability, it’s figuring out how to do things better. If that sounds like you too, &lt;a href=&quot;http://jobs.lickability.com/&quot;&gt;&lt;strong&gt;come work with us&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt; </content:encoded><author>Jillian Meehan</author></item><item><title>5 Tips for Reporting Bugs &amp; Feedback to Apple</title><link>https://lickability.com/blog/5-tips-for-filing-radars/</link><guid isPermaLink="true">https://lickability.com/blog/5-tips-for-filing-radars/</guid><description>How to get your bug reports on Apple’s radar</description><pubDate>Thu, 07 Feb 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you’ve been a developer on Apple platforms like iOS and macOS for a while, you’re undoubtably familiar with an internal tool known as “&lt;a href=&quot;https://www.theiphonewiki.com/wiki/Radar&quot;&gt;Radar&lt;/a&gt;.” Radar is a bug tracker that Apple builds, maintains, and uses to track nearly every piece of software engineering work that’s necessary to release their operating systems and devices. And sometimes, when you as a third-party developer, encounter a bug in the OS or frameworks you’re using, you might be encouraged (well, shouted at) to FILE A RADAR! Or even “&lt;a href=&quot;https://blackpixel.com/writing/2012/02/radar-or-gtfo.html&quot;&gt;Radar or GTFO&lt;/a&gt;.”&lt;/p&gt;&lt;p&gt;While Radar has &lt;a href=&quot;http://fixradarorgtfo.com&quot;&gt;its critics&lt;/a&gt;, and we’re often among them, it has been a useful tool for us to report issues over the years—many of which have been fixed by Apple.&lt;/p&gt;&lt;p&gt;So how do you file a radar? It’s as simple as filling in a single form in Apple’s &lt;a href=&quot;https://bugreport.apple.com&quot;&gt;Bug Reporter&lt;/a&gt;, as long as you’ve got a developer account &lt;a href=&quot;#footnote-83903ba8768a&quot; id=&quot;ref-footnote-83903ba8768a&quot; class=&quot;not-prose inline-grid place-content-center size-5 text-sm font-semibold bg-blueRaspberry/50 rounded-sm text-berryBlue -translate-y-0.5&quot; aria-label=&quot;Go to footnote 1&quot;&gt; 1 &lt;/a&gt;. But what makes Apple more likely to pay attention to &lt;em&gt;your&lt;/em&gt; bug when thousands are pouring in? 10 years of radar-filing and nearly a hundred radars later, we have a few tricks up our sleeve.&lt;/p&gt;&lt;h3&gt;1. Reproduce the issue 📋&lt;/h3&gt;&lt;p&gt;There’s nothing worse than being assigned a bug to fix that you can’t even make happen. So before you file your report with Apple, you’ll want to make sure you can make it happen more than once. Once you know the steps necessary to “repro” the bug, and have confirmed that it’s not an issue with your own app, try to whittle it down to the smallest number of steps or factors necessary to get it to happen.&lt;/p&gt;&lt;p&gt;Find the “smallest reproducible case” and then move on to tip number two.&lt;/p&gt;&lt;h3&gt;2. Include a sample project 📎&lt;/h3&gt;&lt;p&gt;Apple doesn’t want to hunt through your entire app, and you don’t want to go around uploading your company’s source code on random web forms. That’s a no-no. So instead, try to make a tiny app dedicated to making the bug happen, and include that with your report instead of your hundred-thousand line codebase.&lt;/p&gt;&lt;p&gt;Your sample project can be super barebones and sometimes even fit into a &lt;a href=&quot;https://www.apple.com/swift/playgrounds/&quot;&gt;Playground&lt;/a&gt;, but feel free to spice up your radars with humor, instructions within the app, and more if you want to. If only we had the time. 🤷‍♂&lt;/p&gt;&lt;h3&gt;3. Attach logs 📑&lt;/h3&gt;&lt;p&gt;Apple will often also request a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;[sysdiagnose](https://developer.apple.com/bug-reporting/profiles-and-logs/?name=sysdiagnose)&lt;/code&gt; log with your report. Sysdiagnose is a built-in OS-level tool that can collect detailed logs that the engineer on the other side might find very helpful in their debugging of your issue. If you’re having a problem with a particular framework like CloudKit or Photos, you can also find &lt;a href=&quot;https://developer.apple.com/bug-reporting/profiles-and-logs/&quot;&gt;profiles and instructions for enabling additional logging&lt;/a&gt; on the developer portal. You’ll save some back-and-forth if you turn those on and include them in your initial report.&lt;/p&gt;&lt;h3&gt;4. Publicize your report 📣&lt;/h3&gt;&lt;p&gt;Radars are normally private, for good reasons: reports sometimes contain security and privacy-sensitive information that Apple wouldn’t want to inadvertently leak. But every radar has a unique ID number, and if you’re sure that there’s no corporate or personal secrets, you can use that number to file a public version on &lt;a href=&quot;https://openradar.appspot.com/&quot;&gt;Open Radar&lt;/a&gt;, an open-source community resource. When you link to the radar on Twitter or in Slack, it’s a best practice to include the Open Radar link and the &lt;strong&gt;rdar://bugid&lt;/strong&gt; style link so Apple folks can open it right up inside their native Radar app for macOS and iOS.&lt;/p&gt;&lt;p&gt;Since Open Radar doesn’t host files itself, we recommend uploading your attachments to Dropbox, Google Drive, or GitHub and linking to them from there. Also, once Apple responds to your bug, it’s on you to mirror that on Open Radar so the larger community knows when it’s fixed, or (more commonly) marked as a duplicate of another report.&lt;/p&gt;&lt;h3&gt;5. Know people at Apple 🤝&lt;/h3&gt;&lt;p&gt;I know, I know…easier said than done. Even though this might not be realistic for every developer, it really does help to build relationships with Apple engineers at WWDC and &lt;a href=&quot;https://developer.apple.com/contact/index.html&quot;&gt;Tech Talks&lt;/a&gt;, on Twitter, and in the &lt;a href=&quot;https://developer.apple.com/devforums/&quot;&gt;Developer Forums&lt;/a&gt;. We’ve found that our radars tend to get treated with a little more urgency by having folks to email or DM who work at Apple. There’s also a trend within the company informally called &lt;em&gt;radar-stalking&lt;/em&gt; where Apple engineers can set up a filter to be notified whenever an individual reporter or dev team files an issue. So the more folks you get to know, the better your chances that someone might decide to watch your account for fun!&lt;/p&gt;&lt;h3&gt;In Closing&lt;/h3&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A cartoon illustration of an anteater&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9bae0617c934ffd197938aa8b976a7ab8a3f6d6a-256x256.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=128 128w, https://cdn.sanity.io/images/nkt6o869/production/9bae0617c934ffd197938aa8b976a7ab8a3f6d6a-256x256.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=256 256w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9bae0617c934ffd197938aa8b976a7ab8a3f6d6a-256x256.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=256&quot; width=&quot;256&quot; height=&quot;256&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;The mascot for Radar is an anteater. It eats bugs, get it?&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Filing radars is a pain and sometimes it feels like shouting into a black hole. If you need more urgent help or a workaround, don’t be afraid to use a &lt;a href=&quot;https://developer.apple.com/support/technical/&quot;&gt;Technical Support Incident&lt;/a&gt; (TSI) if your radar isn’t getting traction fast enough. That’s what the &lt;a href=&quot;https://developer.apple.com/contact/index.html&quot;&gt;Developer Technical Support&lt;/a&gt; team is there for.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Looking for more tips on filing radars that will give Apple everything they need to fix your bug? Check out &lt;a href=&quot;https://developer.apple.com/bug-reporting&quot;&gt;Apple’s instructions&lt;/a&gt; and these posts from our &lt;a href=&quot;https://pspdfkit.com/blog/2016/writing-good-bug-reports/&quot;&gt;friends at PSPDFKit&lt;/a&gt; and &lt;a href=&quot;https://www.imore.com/radar-or-gtfo&quot;&gt;Rene Ritchie at iMore&lt;/a&gt;. If you have tips of your own that we should know about, &lt;strong&gt;tell us all about them on Twitter at &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;@lickability&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;  &lt;/aside&gt; &lt;footer class=&quot;footnotes prose-base mt-fl-lg&quot; aria-label=&quot;Footnotes&quot;&gt; &lt;ol class=&quot;list-none p-0 grid grid-cols-[auto,1fr] gap-x-fl-2xs gap-y-fl-sm items-baseline&quot;&gt;  &lt;a href=&quot;#ref-footnote-83903ba8768a&quot; id=&quot;footnote-83903ba8768a&quot; class=&quot;not-prose inline-grid place-content-center size-5 text-sm font-semibold bg-blueRaspberry/50 rounded-sm text-berryBlue -translate-y-0.5&quot; aria-label=&quot;Back to reference 1&quot;&gt; 1 &lt;/a&gt; &lt;div class=&quot;footnote-content *:inline *:p-0&quot;&gt; &lt;p&gt;If your issue is with the Swift programming language specifically, you may be able to skip Radar and &lt;a href=&quot;https://swift.org/contributing/#reporting-bugs&quot;&gt;file a bug&lt;/a&gt; in the project’s &lt;a href=&quot;https://bugs.swift.org&quot;&gt;public JIRA project&lt;/a&gt;.&lt;/p&gt; &lt;/div&gt;  &lt;/ol&gt; &lt;/footer&gt;</content:encoded><author>mb bischoff</author></item><item><title>A Lickability New Year</title><link>https://lickability.com/blog/a-lickability-new-year/</link><guid isPermaLink="true">https://lickability.com/blog/a-lickability-new-year/</guid><description>🍾 🎉 🥂 📸 🍕 🎊 🍹</description><pubDate>Thu, 17 Jan 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We’re already *checks notes* three weeks into 2019, so it’s a little late to dwell on what we accomplished, learned, and loved as a company last year. (If you need a refresher, check out our &lt;a href=&quot;https://blog.lickability.com/archive/2018&quot;&gt;blog archive&lt;/a&gt;.) Right now, we’re ready to focus on our goals for the year ahead — and we’ve got plenty of them.&lt;/p&gt;&lt;p&gt;One of our goals this year is to continue building the Lickability community, both &lt;a href=&quot;https://blog.lickability.com/slack-is-for-friends-too-57ab3f9d9da0&quot;&gt;online&lt;/a&gt; and offline. And what better way to do that than by throwing a holiday party in &lt;a href=&quot;https://blog.lickability.com/our-new-office-is-official-88fc37eb3ef0&quot;&gt;our new office&lt;/a&gt;? So last week, we invited a bunch of our NYC pals to Lickability HQ to celebrate the new year with us.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d63781d82063d047a4b00cd320792a88d218d99e-1536x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=384 384w, https://cdn.sanity.io/images/nkt6o869/production/d63781d82063d047a4b00cd320792a88d218d99e-1536x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=768 768w, https://cdn.sanity.io/images/nkt6o869/production/d63781d82063d047a4b00cd320792a88d218d99e-1536x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1152 1152w, https://cdn.sanity.io/images/nkt6o869/production/d63781d82063d047a4b00cd320792a88d218d99e-1536x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1536 1536w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d63781d82063d047a4b00cd320792a88d218d99e-1536x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1536&quot; width=&quot;1536&quot; height=&quot;1536&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f562f4a1af76bfafe4a1b4c5973b16713a967b01-2044x2044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/f562f4a1af76bfafe4a1b4c5973b16713a967b01-2044x2044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/f562f4a1af76bfafe4a1b4c5973b16713a967b01-2044x2044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/f562f4a1af76bfafe4a1b4c5973b16713a967b01-2044x2044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/f562f4a1af76bfafe4a1b4c5973b16713a967b01-2044x2044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/f562f4a1af76bfafe4a1b4c5973b16713a967b01-2044x2044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2044 2044w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f562f4a1af76bfafe4a1b4c5973b16713a967b01-2044x2044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1600&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/301a7f5dbac0a3e2df73e6bdd81c9774bee259fe-2047x2047.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/301a7f5dbac0a3e2df73e6bdd81c9774bee259fe-2047x2047.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/301a7f5dbac0a3e2df73e6bdd81c9774bee259fe-2047x2047.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/301a7f5dbac0a3e2df73e6bdd81c9774bee259fe-2047x2047.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/301a7f5dbac0a3e2df73e6bdd81c9774bee259fe-2047x2047.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/301a7f5dbac0a3e2df73e6bdd81c9774bee259fe-2047x2047.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2047 2047w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/301a7f5dbac0a3e2df73e6bdd81c9774bee259fe-2047x2047.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1600&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b090c9a1473c3f493f8439d1cfb429e38a2979ba-1537x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=384 384w, https://cdn.sanity.io/images/nkt6o869/production/b090c9a1473c3f493f8439d1cfb429e38a2979ba-1537x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=769 769w, https://cdn.sanity.io/images/nkt6o869/production/b090c9a1473c3f493f8439d1cfb429e38a2979ba-1537x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1153 1153w, https://cdn.sanity.io/images/nkt6o869/production/b090c9a1473c3f493f8439d1cfb429e38a2979ba-1537x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1537 1537w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b090c9a1473c3f493f8439d1cfb429e38a2979ba-1537x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1537&quot; width=&quot;1537&quot; height=&quot;1536&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/cfcaf7e309cad5be1f2491c9f7bfe41bacb12cae-1536x1535.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=384 384w, https://cdn.sanity.io/images/nkt6o869/production/cfcaf7e309cad5be1f2491c9f7bfe41bacb12cae-1536x1535.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=768 768w, https://cdn.sanity.io/images/nkt6o869/production/cfcaf7e309cad5be1f2491c9f7bfe41bacb12cae-1536x1535.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1152 1152w, https://cdn.sanity.io/images/nkt6o869/production/cfcaf7e309cad5be1f2491c9f7bfe41bacb12cae-1536x1535.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1536 1536w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/cfcaf7e309cad5be1f2491c9f7bfe41bacb12cae-1536x1535.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1536&quot; width=&quot;1536&quot; height=&quot;1535&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f366722cdc4663079001b303493c87b2b90e9b4c-2044x2044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/f366722cdc4663079001b303493c87b2b90e9b4c-2044x2044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/f366722cdc4663079001b303493c87b2b90e9b4c-2044x2044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/f366722cdc4663079001b303493c87b2b90e9b4c-2044x2044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/f366722cdc4663079001b303493c87b2b90e9b4c-2044x2044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/f366722cdc4663079001b303493c87b2b90e9b4c-2044x2044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2044 2044w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f366722cdc4663079001b303493c87b2b90e9b4c-2044x2044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1600&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;We had plenty of good pizza (c/o Williamsburg Pizza 🍕), good drinks (c/o of Cristhian and Eli from The NoMad Bar 🍹), and good snacks (c/o the grocery store around the corner 🥨). Between catching up with our friends, playing board games—including a secret prototype game, &lt;em&gt;shhhh&lt;/em&gt;—and taking selfies in the makeshift photo booth, there were lots of great memories to be made.&lt;/p&gt;&lt;p&gt;Now that we’ve started 2019 off on the right foot, we want to thank you. Your continued support and friendship really means a lot to us. Thanks for being part of the Lickability family! ❤️&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>3 Tools to Stay on Top of Pull Requests</title><link>https://lickability.com/blog/3-tools-to-stay-on-top-of-pull-requests/</link><guid isPermaLink="true">https://lickability.com/blog/3-tools-to-stay-on-top-of-pull-requests/</guid><description>Review code like a pro with these three apps 🛠</description><pubDate>Thu, 10 Jan 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here at &lt;a href=&quot;https://lickability.com&quot;&gt;Lickability&lt;/a&gt;, we’re often working on many software projects at once: &lt;a href=&quot;https://lickability.com/clients&quot;&gt;client products&lt;/a&gt;, &lt;a href=&quot;https://github.com/Lickability/PinpointKit&quot;&gt;internal tools&lt;/a&gt;, and &lt;a href=&quot;https://lickability.com/products&quot;&gt;new apps of our own&lt;/a&gt;. That means a lot of issues, pull requests, and discussions about code come up throughout the work week. Naturally, it can get overwhelming if we’re constantly context-switching or checking to see if there’s something new for us to review or chime in on.&lt;/p&gt;&lt;p&gt;We use 3 tools that make the cacophony of code review a lot more manageable. If you’re a pull requests power-user like us, check these out.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://steamclock.com/quests/&quot;&gt;Quests&lt;/a&gt;&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A screenshot of the menu bar app Quests.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/45a98501b3375b9cad08e2bc55ff3acbaa4c5fd2-800x300.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/45a98501b3375b9cad08e2bc55ff3acbaa4c5fd2-800x300.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/45a98501b3375b9cad08e2bc55ff3acbaa4c5fd2-800x300.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/45a98501b3375b9cad08e2bc55ff3acbaa4c5fd2-800x300.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/45a98501b3375b9cad08e2bc55ff3acbaa4c5fd2-800x300.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800&quot; width=&quot;800&quot; height=&quot;300&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;The most recent addition to our tool belt is Quests, from our friends at &lt;a href=&quot;https://steamclock.com&quot;&gt;Steamclock Software&lt;/a&gt;. Quests is a macOS menu bar app that can be configured to show you how many pull requests and/or issues you’re the blocker on. A few of us keep an eye on it throughout the day, and when that number starts creeping up, we jump over to GitHub (or GitLab, which it also supports) to help our coworkers get their questions answered.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://itunes.apple.com/us/app/quests/id1447415753?mt=12&quot;&gt;Get Quests on the Mac App Store&lt;/a&gt; for free. Tell ’em Lickability sent you. ⚔️&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;http://githawk.com/&quot;&gt;GitHawk&lt;/a&gt;&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/fa8083a82da138db008e6247fb1ea0e5e982ca5b-600x1064.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w, https://cdn.sanity.io/images/nkt6o869/production/fa8083a82da138db008e6247fb1ea0e5e982ca5b-600x1064.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/fa8083a82da138db008e6247fb1ea0e5e982ca5b-600x1064.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600&quot; width=&quot;600&quot; height=&quot;1064&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Sometimes we want to check in on our GitHub notifications when we’re not at our desks. On the way to work, when we’re at a conference, or (rarely) to peek at product progress on vacation. 👀&lt;/p&gt;&lt;p&gt;That’s where GitHawk comes into play. It’s a full-featured iOS app written by fellow New Yorker, &lt;a href=&quot;https://twitter.com/_ryannystrom&quot;&gt;Ryan Nystrom&lt;/a&gt;. GitHawk lets us process notifications, reply to issues, and merge pull requests all from our phones. With great power comes great responsibility, though — don’t use GitHawk to shirk your responsibility of thoroughly reviewing code. It’s best used if you’ve already done a review and want to merge something on your way home once the tests pass.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://itunes.apple.com/us/app/githawk-for-github/id1252320249?ls=1&amp;mt=8https://itunes.apple.com/us/app/githawk-for-github/id1252320249?ls=1&amp;mt=8&quot;&gt;GitHawk is free on the App Store&lt;/a&gt; and open source &lt;a href=&quot;https://github.com/GitHawkApp/GitHawk&quot;&gt;on GitHub&lt;/a&gt;. 🦅&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://github.com/danger/danger&quot;&gt;Danger&lt;/a&gt;&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;The logo of Danger&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/8892618261ad155999f42678f8028752f3b366d9-1200x382.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w, https://cdn.sanity.io/images/nkt6o869/production/8892618261ad155999f42678f8028752f3b366d9-1200x382.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/8892618261ad155999f42678f8028752f3b366d9-1200x382.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=900 900w, https://cdn.sanity.io/images/nkt6o869/production/8892618261ad155999f42678f8028752f3b366d9-1200x382.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/8892618261ad155999f42678f8028752f3b366d9-1200x382.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200&quot; width=&quot;1200&quot; height=&quot;382&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Rather than running on your device like the above tools, Danger is a web application that runs as part of your continuous integration system. It’s a helpful little robot that makes the first pass on every pull request so humans don’t have to. Danger can look for things like common anti-patterns, missing PR descriptions, and a lot more. If you’re still making style and formatting comments manually like it’s 2009, level up your workflow with Danger and save the humans on your team a ton of time.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://danger.systems/&quot;&gt;Danger is available in JS, Swift, and Ruby&lt;/a&gt;. Pick your poision. 🧪&lt;/p&gt;&lt;p&gt;Pull requests make code review easy, and peer review &lt;a href=&quot;http://static1.1.sqspcdn.com/static/f/702523/9242263/1288742124060/200806-0-Issue.pdf?token=93JzPoUrFN%2FZX1l7lbRO2nEs0OI%3D&quot;&gt;has been shown to be hugely beneficial&lt;/a&gt; to ensuring the correctness of code. But sometimes there’s just too much to stay on top of. We’ve found that these tools make it a lot easier to keep things moving. Try them out and let us know what you think on Twitter at &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;@lickability&lt;/a&gt;. Happy reviewing!&lt;/p&gt; </content:encoded><author>mb bischoff</author></item><item><title>The Magic of the Everybody Meeting</title><link>https://lickability.com/blog/the-magic-of-the-everybody-meeting/</link><guid isPermaLink="true">https://lickability.com/blog/the-magic-of-the-everybody-meeting/</guid><description>No company secrets were harmed in the making of this blog post.</description><pubDate>Thu, 13 Dec 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Whether you love them or hate them, you have to live with them. Sometimes they’re 10 minutes long, and sometimes they take up an entire hour. Sometimes they’re amazingly productive, and sometimes they would have been better off as a Slack message. You know what I’m talking about — meetings. At Lickability, we have a very special one that we look forward to every week: the Everybody Meeting.&lt;/p&gt;&lt;h3&gt;Lickability Fridays&lt;/h3&gt;&lt;p&gt;We dedicate the last day of every week to what we call “Lickability Friday.” Four out of five days a week, we spend our time working on client projects — but on Lickability Fridays, the whole company takes a breath to focus on what’s going on in our own office (inspired by our friends at &lt;a href=&quot;https://thoughtbot.com/playbook/our-company/time&quot;&gt;Thoughtbot&lt;/a&gt;). We work on our &lt;a href=&quot;https://lickability.com/products&quot;&gt;products&lt;/a&gt;, we have a team lunch at a nearby restaurant, and we have, of course, the Everybody Meeting.&lt;/p&gt;&lt;p&gt;The Everybody Meeting is exactly what it sounds like: everybody at the company gathers in the conference room, where we go over the details of what happened that week — we review every client project, internal product, and department one by one. This might sound tedious, but it’s also incredibly important. As a small company, we function best when everyone knows what everyone else is working on. The Everybody Meeting creates space for that to happen, and for people to ask questions, hear outside opinions, and learn from each other on a regular basis.&lt;/p&gt;&lt;h3&gt;How it works&lt;/h3&gt;&lt;p&gt;The structure of our meetings isn’t set in stone, so it’s able to change week to week based on our needs. That said, we like to think we’ve nailed down what works the best for us. We use a Notion template with headers for every topic we want to cover, and bulleted prompts that detail how we want to cover them. A few of the specific things we go over are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Last week&lt;/strong&gt;: To refresh everyone’s memory on what was going on a week ago, and to make sure the concerns we had last week have been dealt with.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Good stuff&lt;/strong&gt;: To celebrate the victories, big and small.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Concerns&lt;/strong&gt;: To encourage people to bring up issues and find a way to fix them, together.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;To do&lt;/strong&gt;: To make sure the things that need to get done are taken care of. Every Friday morning, we fill out each section in the document and read over what others wrote. Then, when it comes time to gather together in the conference room, we go over the document together and ask questions about what we’re curious or confused about, figure out how to deal with specific concerns, and assign tasks for the week ahead.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Why it works&lt;/h3&gt;&lt;p&gt;There’s no one good way to do meetings, because there are &lt;em&gt;lots&lt;/em&gt;of ways to do meetings and every company’s needs are different. What works for us might not work for you! And what works for us this year might not even work for us next year. When it comes to meetings (as with most things), it’s important to recognize that you could probably always be doing the thing you’re doing better.&lt;/p&gt;&lt;p&gt;So far, this structure has been working great for us. It’s thorough, but streamlined, and keeps our meetings both productive and quick — usually, it takes us about 30 minutes to get through everything before heading off to lunch together. We don’t have a lot of meetings at Lickability, so we try to keep the ones we do have as productive, flexible, and helpful as possible. Using a meeting template and sticking to a similar routine every week, while still allowing for change and encouraging discussion, has helped us get the most out of our weekly team meetings&lt;/p&gt;&lt;p&gt;We’ve all been in plenty of bad meetings — meetings with no agenda, meetings that are longer than they should be, and meetings that don’t seem to get anything done at all. If you find yourself stuck in a rut with unproductive, time-wasting meetings, remember: it doesn’t have to be that way. Our advice? Figure out what exactly it is you want to get out of each meeting, put some effort into making it happen, and find a routine that works well for your needs.&lt;/p&gt;&lt;p&gt;And yeah, in some cases, that meeting should totally just be an email.&lt;/p&gt; </content:encoded><author>Jillian Meehan</author></item><item><title>5 of Our Favorite Blogs</title><link>https://lickability.com/blog/5-of-our-favorite-blogs/</link><guid isPermaLink="true">https://lickability.com/blog/5-of-our-favorite-blogs/</guid><description>And the folks that write them 📝</description><pubDate>Thu, 29 Nov 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Writing &lt;a href=&quot;https://lickability.com/products&quot;&gt;apps&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;tweets&lt;/a&gt; isn’t all we do here at Lickability. We also love discovering new independent writers and following their blogs in RSS and on Twitter. I recently asked the team for some recommendations to add to my feed reader and shared some of my own. Here are the blogs we can’t get enough of:&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://nshipster.com/&quot;&gt;NSHipster&lt;/a&gt;&lt;/h3&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/0462d7c5ccbd7dba98d01ca152c1f07942815b5f-400x400.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/0462d7c5ccbd7dba98d01ca152c1f07942815b5f-400x400.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/0462d7c5ccbd7dba98d01ca152c1f07942815b5f-400x400.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Almost every iOS engineer at the company mentioned reading NSHipster. Now that the site’s founder, &lt;a href=&quot;https://twitter.com/mattt&quot;&gt;Mattt Thompson&lt;/a&gt;, is back at the helm and publishing regularly, we’re re-energized and excited to learn obscure Cocoa and Swift APIs, answer trivia questions (some of us even &lt;a href=&quot;https://nshipster.com/nshipster-quiz-3/&quot;&gt;won first place&lt;/a&gt; at an NSHipster Pub trivia night a few yeas ago), and find ways to apply that new knowledge to our work.&lt;/p&gt;&lt;p&gt;Favorite past topics include &lt;a href=&quot;https://nshipster.com/nslinguistictagger/&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;NS​Linguistic​Tagger&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://nshipster.com/uikeycommand/&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;UI​Key​Command&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&quot;https://nshipster.com/void/&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;Void&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://usesthis.com/&quot;&gt;Uses This&lt;/a&gt;&lt;/h3&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/23810fda0d54d8c076abf5685c1456ad70d4b7f5-400x400.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/23810fda0d54d8c076abf5685c1456ad70d4b7f5-400x400.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/23810fda0d54d8c076abf5685c1456ad70d4b7f5-400x400.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;One of my personal favorites, Uses This (née The Setup), by &lt;a href=&quot;https://waferbaby.com/@d&quot;&gt;Daniel Brogan&lt;/a&gt;, is a long-running interview series with curious nerds, creatives, and hackers about the software and hardware they use to get their job done. It’s fascinating to get a window into the obsessive and eccentric workspaces of the people we admire and how they use those tools to do what they do.&lt;/p&gt;&lt;p&gt;Some interviewees and setups we loved: &lt;a href=&quot;https://usesthis.com/interviews/sara.mauskopf/&quot;&gt;Sara Mauskopf&lt;/a&gt;, &lt;a href=&quot;https://usesthis.com/interviews/karlee.esmailli/&quot;&gt;Karlee Esmailli&lt;/a&gt;, and &lt;a href=&quot;https://usesthis.com/interviews/stephen.hackett/&quot;&gt;Stephen Hackett&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://talk.objc.io/&quot;&gt;Swift Talk&lt;/a&gt;&lt;/h3&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a86abaef0d562e58f12b01d95aaa72c64da58369-400x400.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/a86abaef0d562e58f12b01d95aaa72c64da58369-400x400.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a86abaef0d562e58f12b01d95aaa72c64da58369-400x400.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Video blogs count as blogs, right? Michael Amundsen, one of our engineers, loves watching &lt;a href=&quot;https://twitter.com/chriseidhof&quot;&gt;Chris Eidhof&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/floriankugler&quot;&gt;Florian Kugler&lt;/a&gt; dive into complex architectural challenges and come out on the other side with elegant solutions in Swift. It costs $15 a month to subscribe (half of their videos are available for free), but it’s well worth it if you learn best by watching and want to master the craft.&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://larahogan.me/&quot;&gt;Laura Hogan&lt;/a&gt;&lt;/h3&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6903458b83735b64f6e5b5d7a8a33b6ecd4ab0d2-400x400.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/6903458b83735b64f6e5b5d7a8a33b6ecd4ab0d2-400x400.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6903458b83735b64f6e5b5d7a8a33b6ecd4ab0d2-400x400.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;&lt;a href=&quot;https://twitter.com/lara_hogan&quot;&gt;Laura Hogan&lt;/a&gt;’s blog about management, inclusion, and um &lt;a href=&quot;https://larahogan.me/donuts/&quot;&gt;donuts&lt;/a&gt;, is a fairly new discovery to me, but I devour every post as it arrives in &lt;a href=&quot;http://reederapp.com/&quot;&gt;Reeder&lt;/a&gt;. As a coach and consultant, with experience leading teams at Etsy and Kickstarter, Laura writes about technical management in one of the clearest and most empathetic ways I’ve ever seen. It’s a must read if you manage people.&lt;/p&gt;&lt;p&gt;We’re also basing our &lt;a href=&quot;https://larahogan.me/devicelab/&quot;&gt;internal test-device lab&lt;/a&gt; on some early work that she and Destiny Montague did during their time at Etsy! Thanks Laura 🙏&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;http://khanlou.com/&quot;&gt;Khanlou&lt;/a&gt;&lt;/h3&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/8c50af5b1ccf339c09a98f4618351e8f0f36a1bd-400x400.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/8c50af5b1ccf339c09a98f4618351e8f0f36a1bd-400x400.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/8c50af5b1ccf339c09a98f4618351e8f0f36a1bd-400x400.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Our friend and frequent collaborator, &lt;a href=&quot;https://twitter.com/khanlou&quot;&gt;Soroush Khanlou&lt;/a&gt;, writes a well-loved blog at his personal domain that focuses more on architecture and design patterns than the kind of specific snippets of code you might find on Stack Overflow. His posts push us to think deeply and often spring to mind as we were solving particularly thorny problems. He’s also inspired us to publish more ourselves.&lt;/p&gt;&lt;p&gt;Posts like &lt;a href=&quot;http://khanlou.com/2015/05/graduation/&quot;&gt;Graduation&lt;/a&gt;, &lt;a href=&quot;http://khanlou.com/2017/07/refactoring-reveals-truths/&quot;&gt;Refactoring Reveals Truth&lt;/a&gt;, and &lt;a href=&quot;http://khanlou.com/2015/07/state-negotiations/&quot;&gt;State Negotiations&lt;/a&gt; really stuck with us.&lt;/p&gt;&lt;p&gt;That’s all for now! The blogs we come back to over and over again are the ones that challenge us to look critically about our work and our workflows. They lean a bit technical, but their writing is anything but dry.&lt;/p&gt; </content:encoded><author>mb bischoff</author></item><item><title>Switching to Notion</title><link>https://lickability.com/blog/switching-to-notion/</link><guid isPermaLink="true">https://lickability.com/blog/switching-to-notion/</guid><description>How we set up our all-in-one workspace</description><pubDate>Thu, 15 Nov 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Before &lt;a href=&quot;https://notion.so/&quot;&gt;Notion&lt;/a&gt;, we were using a lot of different apps to do a lot of different things. We used &lt;a href=&quot;https://basecamp.com&quot;&gt;Basecamp&lt;/a&gt; to assign tasks to team members and keep track of their progress, we used Google Drive to make documents and spreadsheets, and we used &lt;a href=&quot;https://sparkmailapp.com&quot;&gt;Spark&lt;/a&gt; to collaborate on email drafts. None of these are bad tools — in fact, they’re some of our &lt;a href=&quot;https://blog.lickability.com/tools-of-the-trade-18e935713ddf&quot;&gt;favorites&lt;/a&gt; — but we were ready for something new.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e652602b9bf6897314a38f22536662476546a7cd-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/e652602b9bf6897314a38f22536662476546a7cd-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/e652602b9bf6897314a38f22536662476546a7cd-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/e652602b9bf6897314a38f22536662476546a7cd-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/e652602b9bf6897314a38f22536662476546a7cd-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/e652602b9bf6897314a38f22536662476546a7cd-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/e652602b9bf6897314a38f22536662476546a7cd-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/e652602b9bf6897314a38f22536662476546a7cd-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e652602b9bf6897314a38f22536662476546a7cd-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1212&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Our Notion workspace has everything we need: meeting notes, company policy documents, on boarding checklists, and more.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;We started out just wanting a space where we could collaborate on various documents — meeting notes, email drafts, blog posts, and other short, simple things that don’t generally need any sort of special formatting. As an experiment, we gave Notion a try, starting with a “Documents” page for lightweight spreadsheets and company policy docs, and an “Email Scratchpad” to keep track of and collaborate on emails together.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/5c6e6870a5b3afc17f28a28c4630cda230b1912b-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/5c6e6870a5b3afc17f28a28c4630cda230b1912b-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/5c6e6870a5b3afc17f28a28c4630cda230b1912b-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/5c6e6870a5b3afc17f28a28c4630cda230b1912b-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/5c6e6870a5b3afc17f28a28c4630cda230b1912b-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/5c6e6870a5b3afc17f28a28c4630cda230b1912b-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/5c6e6870a5b3afc17f28a28c4630cda230b1912b-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/5c6e6870a5b3afc17f28a28c4630cda230b1912b-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/5c6e6870a5b3afc17f28a28c4630cda230b1912b-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1212&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Notion gives us easy access to all of our meeting notes, blog posts, and email drafts. If you’ve gotten an email from us in the past three months, we probably drafted it together in Notion.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/8b1a1dd79a42655ec584a6c779d66c329c313572-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/8b1a1dd79a42655ec584a6c779d66c329c313572-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/8b1a1dd79a42655ec584a6c779d66c329c313572-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/8b1a1dd79a42655ec584a6c779d66c329c313572-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/8b1a1dd79a42655ec584a6c779d66c329c313572-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/8b1a1dd79a42655ec584a6c779d66c329c313572-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/8b1a1dd79a42655ec584a6c779d66c329c313572-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/8b1a1dd79a42655ec584a6c779d66c329c313572-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/8b1a1dd79a42655ec584a6c779d66c329c313572-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1212&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;No company secrets were harmed in the making of this screenshot—here’s an example of what our To Do page looks like. We keep track of tasks by category (Blog, Finance, Sales, etc.) and by status (Blocked, On Deck, In Progress, and Completed).&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Notion is supposed to be an “all-in-one workspace” that can replace seemingly every tool you’re already using at work every day. So, as our experiment went on, we started moving more and more of our work into Notion. We made a “To Do” page to assign and keep track of company tasks, we added an easy-to-use meeting template for our weekly team meetings, and we made a “Home Base” with quick links to all of our new pages.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e8232ee8ca50328a288e3269dae1f2d3541c8afa-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/e8232ee8ca50328a288e3269dae1f2d3541c8afa-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/e8232ee8ca50328a288e3269dae1f2d3541c8afa-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/e8232ee8ca50328a288e3269dae1f2d3541c8afa-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/e8232ee8ca50328a288e3269dae1f2d3541c8afa-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/e8232ee8ca50328a288e3269dae1f2d3541c8afa-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/e8232ee8ca50328a288e3269dae1f2d3541c8afa-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/e8232ee8ca50328a288e3269dae1f2d3541c8afa-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e8232ee8ca50328a288e3269dae1f2d3541c8afa-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1212&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;TGIF: We have a database of all of our weekly meeting notes, a template for starting a new meeting, a space to write our individual self-evaluations every week, and a helpful spreadsheet of our favorite places to get lunch together.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Eventually, the experiment with Notion had to come to a conclusion. Could it replace all of our other tools completely? No. But could it replace &lt;em&gt;some &lt;/em&gt;of them and become a really useful workspace? For sure. So far, we’ve said goodbye to Basecamp and started using Notion for all of our task management. We no longer use Spark for email collaboration (though some people in the office still love it!) and use our Email Scratchpad in Notion instead. And aside from legal papers and contracts, a lot of our docs and spreadsheets are now created and stored in Notion rather than Google Drive. (For example, this blog post? Drafted and edited in Notion.)&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/16d208c3b6e5562915e718d70df4edff32e09c44-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/16d208c3b6e5562915e718d70df4edff32e09c44-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/16d208c3b6e5562915e718d70df4edff32e09c44-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/16d208c3b6e5562915e718d70df4edff32e09c44-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/16d208c3b6e5562915e718d70df4edff32e09c44-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/16d208c3b6e5562915e718d70df4edff32e09c44-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/16d208c3b6e5562915e718d70df4edff32e09c44-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/16d208c3b6e5562915e718d70df4edff32e09c44-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/16d208c3b6e5562915e718d70df4edff32e09c44-3220x2440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1212&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;We didn’t have a good system for keeping track of blog posts before Notion. Now, we have a space where we can brainstorm, assign, draft, and schedule new blog posts. &lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;No tool is perfect, of course. Notion isn’t as powerful as other apps for keeping track of to-dos, formatting with markdown and the built-in slash commands can be tricky if you aren’t used to it, and the iOS app needs some work (@notion, &lt;a href=&quot;https://lickability.com/contact&quot;&gt;hire us&lt;/a&gt; 😉). But so far, Notion has definitely proven itself worthy of being the all-in-one tool we didn’t know we needed. It’s beautiful, it’s flexible, and it’s intuitive. It’s also &lt;em&gt;extremely&lt;/em&gt; emoji-friendly, which is always a plus.&lt;/p&gt;&lt;p&gt;As Notion continues to grow and we continue to experiment with different ways of using it, our workspace will likely adapt and change over time. It’s important that we are as flexible as the tools we use. That’s why we encourage you to give Notion a try for yourself — if you need a little help getting started, check out the live demo on their &lt;a href=&quot;http://notion.so/&quot;&gt;website&lt;/a&gt;, and their “&lt;a href=&quot;https://www.notion.so/Getting-started-with-templates-bb41254105cd48b7ad69a6bebad7a08c&quot;&gt;Getting started with templates&lt;/a&gt;” post. Happy Notion-ing! 🥳&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;If you haven’t already, we recommend giving Notion a try. You can sign up &lt;a href=&quot;https://www.notion.so/?r=7b16308cbeab456fa4b7c4434c5744f2&quot;&gt;here&lt;/a&gt; to get $10 in credit when you set up an account using our referral link.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Jillian Meehan</author></item><item><title>The Many (Watch) Faces of Lickability</title><link>https://lickability.com/blog/the-many-watch-faces-of-lickability/</link><guid isPermaLink="true">https://lickability.com/blog/the-many-watch-faces-of-lickability/</guid><description>A look at our Apple Watch faces ⌚️</description><pubDate>Thu, 08 Nov 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few posts back, we showed you the Lickability team’s &lt;a href=&quot;https://blog.lickability.com/home-sweet-home-screen-a93f1eb83791&quot;&gt;iPhone home screens&lt;/a&gt; — and &lt;a href=&quot;https://twitter.com/lickability/status/1045744127839735808&quot;&gt;you showed us some of yours&lt;/a&gt;. This time around, we’re sharing our Apple Watch Faces. Strap in, folks.&lt;/p&gt;&lt;h3&gt;mb Bischoff&lt;/h3&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/aff2d7a7bc1cc3c87115b232de96fa22148cd536-277x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=139 139w, https://cdn.sanity.io/images/nkt6o869/production/aff2d7a7bc1cc3c87115b232de96fa22148cd536-277x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=277 277w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/aff2d7a7bc1cc3c87115b232de96fa22148cd536-277x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=277&quot; width=&quot;277&quot; height=&quot;480&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;I’m a purist (read: elitist). I still love the Utility face that shipped on the Apple Watch Series 0. Four complications keep me aware of everything going on in my day: my next calendar event via &lt;a href=&quot;https://flexibits.com/fantastical&quot;&gt;Fantastical&lt;/a&gt;, temperature and weather conditions from the not-so-friendly A.I. of &lt;a href=&quot;https://www.meetcarrot.com/weather/&quot;&gt;CARROT Weather&lt;/a&gt;, my &lt;a href=&quot;https://www.omnigroup.com/omnifocus/&quot;&gt;OmniFocus&lt;/a&gt; to-dos, and the date. The second hand and minute markers are set in the signature Hermés orange, because I’m a sucker for exclusives and spent too much money on this watch.&lt;/p&gt;&lt;h3&gt;Michael Liberatore&lt;/h3&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a213ebe11fb503e9665cf6359f4b657ecd4690c8-277x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=139 139w, https://cdn.sanity.io/images/nkt6o869/production/a213ebe11fb503e9665cf6359f4b657ecd4690c8-277x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=277 277w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a213ebe11fb503e9665cf6359f4b657ecd4690c8-277x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=277&quot; width=&quot;277&quot; height=&quot;480&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;I’m currently using the new Series 4 Infograph Modular watch face on my Apple Watch. While it’s not the prettiest collection or layout of complications, it’s allowed me to have quicker access to more of the things I care about in my daily watch usage.&lt;/p&gt;&lt;p&gt;I care most about Activity, which gets the prime center spot, and Workouts to help fill those activity rings (err… bars in this case, I guess). I love the new weather complication with current, low, and high temperatures, and wouldn’t want a watch face without weather conditions prominently displayed. (Thankfully, there’s enough room for both.) I set timers a lot, frequently via Siri, but I like being able to see the time remaining at a quick glance without having the Timer app full screen.&lt;/p&gt;&lt;p&gt;Oh, and the date and time (duh).&lt;/p&gt;&lt;h3&gt;Michael Amundsen&lt;/h3&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d8e02521a1c08b75cce16757b10a9a87b8839a93-277x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=139 139w, https://cdn.sanity.io/images/nkt6o869/production/d8e02521a1c08b75cce16757b10a9a87b8839a93-277x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=277 277w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d8e02521a1c08b75cce16757b10a9a87b8839a93-277x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=277&quot; width=&quot;277&quot; height=&quot;480&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;It’s a watch, so time and date are fundamental. I like the weather range complication. Activity, because it’s one of the major reasons I have an Apple Watch. Workouts complication for easy access at the gym. I like to use Breathe to help me fall asleep before bed, and I use timers a lot when cooking.&lt;/p&gt;&lt;h3&gt;Jillian Meehan&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f4540576a70f0d3b6c78d43878ac1ed5953535e1-800x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/f4540576a70f0d3b6c78d43878ac1ed5953535e1-800x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/f4540576a70f0d3b6c78d43878ac1ed5953535e1-800x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/f4540576a70f0d3b6c78d43878ac1ed5953535e1-800x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f4540576a70f0d3b6c78d43878ac1ed5953535e1-800x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800&quot; width=&quot;800&quot; height=&quot;480&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;I fully believe in having multiple watch faces to suit your every mood, but the two I use the most are the Infograph Modular face (for everyday) and the Activity Digital face (for workouts).&lt;/p&gt;&lt;p&gt;My main watch face shows me all of the most important things that I need to be able to see at a glance day to day, like my next calendar event, the weather, and how many tasks are left on my &lt;a href=&quot;https://culturedcode.com/things/&quot;&gt;Things&lt;/a&gt; to-do list. Having my Activity rings and the Breathe icon there reminds me to check in with my body now and then throughout the day.&lt;/p&gt;&lt;p&gt;I am by no means a serious athlete, but for times when I want to see a more detailed view of my Activity for the day, check my heart rate, or start a workout (in my case, usually yoga) I like to be able to swipe over to my Activity face.&lt;/p&gt;&lt;h3&gt;Grant Butler&lt;/h3&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/ec301c75fa233e3f541fe46e6386d4d506b01f26-277x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=139 139w, https://cdn.sanity.io/images/nkt6o869/production/ec301c75fa233e3f541fe46e6386d4d506b01f26-277x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=277 277w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/ec301c75fa233e3f541fe46e6386d4d506b01f26-277x480.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=277&quot; width=&quot;277&quot; height=&quot;480&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;I’m currently using the Infograph watch face. It’s a bit of a variation of the Utility face I had been using on my Series 0 watch. I’ve got the current temperature, activity information, and today’s date on there. Since the Infograph face has many more complication slots than the Utility face, I also added the current weather conditions, as well as &lt;a href=&quot;https://junecloud.com&quot;&gt;Deliveries&lt;/a&gt;’ complication.&lt;/p&gt;&lt;h3&gt;…oh, and Brian Capps&lt;/h3&gt;&lt;p&gt;Actual quote from Brian: “What’s an Apple Watch?”&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Our New Office is Official</title><link>https://lickability.com/blog/our-new-office-is-official/</link><guid isPermaLink="true">https://lickability.com/blog/our-new-office-is-official/</guid><description>Peek inside the new Lickability HQ!</description><pubDate>Thu, 01 Nov 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few months ago, all seven of us were sitting side-by-side in a small co-working office. We had enough space for our desks and computers, but that was about it. Not exactly ideal. So, we decided to do something about it and started the search for a new office.&lt;/p&gt;&lt;p&gt;We wanted our new space to feel like an IRL version of our &lt;a href=&quot;https://blog.lickability.com/slack-is-for-friends-too-57ab3f9d9da0&quot;&gt;Slack&lt;/a&gt; — a corner of the NYC tech community that is uniquely ours, where we can have full control over our own workspace, invite folks over for discussions during lunch, and continue to evolve as a company. Our pal &lt;a href=&quot;https://twitter.com/irace&quot;&gt;Bryan Irace&lt;/a&gt; referred us to Elie Reiss at &lt;a href=&quot;https://skylightleasing.com&quot;&gt;Skylight Leasing&lt;/a&gt;, and with Elie’s help, we found the place for us a month later, after trying (and failing) to picture ourselves in other office spaces all over the neighborhood. At the end of August, we left our old office and excitedly moved into the brand new Lickability HQ in Chelsea at 26th St. and 6th Ave.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/ba1f48191a7dbba2bd0cab1f103d720c1e333316-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/ba1f48191a7dbba2bd0cab1f103d720c1e333316-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/ba1f48191a7dbba2bd0cab1f103d720c1e333316-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/ba1f48191a7dbba2bd0cab1f103d720c1e333316-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/ba1f48191a7dbba2bd0cab1f103d720c1e333316-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/ba1f48191a7dbba2bd0cab1f103d720c1e333316-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/ba1f48191a7dbba2bd0cab1f103d720c1e333316-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/ba1f48191a7dbba2bd0cab1f103d720c1e333316-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/ba1f48191a7dbba2bd0cab1f103d720c1e333316-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1066&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Signing the lease on our new place was only the first step. Suddenly, we were in a room several times the size of our old one, and we had almost nothing to fill it with. As eager as we were to leave our co-working space in favor of our own office, there were a lot of advantages to our old place that we never really thought about until we didn’t have them anymore — furniture, food and beverages, cleaning services, conference rooms, etc. were all taken care of for us. When we left, it was up to us to figure it all out ourselves.&lt;/p&gt;&lt;p&gt;We didn’t have any furniture, much less any clue how to decorate an office. So we turned to a friend of the company Megan Leet, at &lt;a href=&quot;https://doitperf.com/&quot;&gt;Perf&lt;/a&gt;, to help us design the space. She worked with us to find all the furniture, decor, and office supplies we needed to create an office that works for us — complete with adjustable-height desks, a comfy couch (which took us a week to decide on), and plants that we are doing our best to keep alive.&lt;/p&gt;&lt;blockquote&gt;Our new office is great, aside from the lack of desks and chairs. 😅 &lt;a href=&quot;https://t.co/E794O3g7Eh&quot;&gt;pic.twitter.com/E794O3g7Eh&lt;/a&gt;&lt;br&gt;&lt;br&gt;— Lickability (@lickability) &lt;a href=&quot;https://twitter.com/lickability/status/1035179218332786688?ref_src=twsrc%5Etfw&quot;&gt;August 30, 2018&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;When we first moved into the office, it was almost completely bare. We had a few large boxes full of everything we took with us from our last place, plus a couple of floor pillows to sit on while we waited for furniture to arrive. Over the course of a few weeks, everything trickled in until we were finally able to sit down at our desks to work every day and have meetings around a table in our conference room. Every day, we thought of something else we needed: sponges, dry erase markers, an umbrella holder.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/537bacaff6848ec5185031d4231be68cc9c32f04-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/537bacaff6848ec5185031d4231be68cc9c32f04-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/537bacaff6848ec5185031d4231be68cc9c32f04-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/537bacaff6848ec5185031d4231be68cc9c32f04-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/537bacaff6848ec5185031d4231be68cc9c32f04-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/537bacaff6848ec5185031d4231be68cc9c32f04-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/537bacaff6848ec5185031d4231be68cc9c32f04-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/537bacaff6848ec5185031d4231be68cc9c32f04-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/537bacaff6848ec5185031d4231be68cc9c32f04-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1188&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/fe3fa4a85d33b9a10f9ef7c56674fa2256ee3469-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/fe3fa4a85d33b9a10f9ef7c56674fa2256ee3469-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/fe3fa4a85d33b9a10f9ef7c56674fa2256ee3469-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/fe3fa4a85d33b9a10f9ef7c56674fa2256ee3469-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/fe3fa4a85d33b9a10f9ef7c56674fa2256ee3469-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/fe3fa4a85d33b9a10f9ef7c56674fa2256ee3469-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/fe3fa4a85d33b9a10f9ef7c56674fa2256ee3469-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/fe3fa4a85d33b9a10f9ef7c56674fa2256ee3469-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/fe3fa4a85d33b9a10f9ef7c56674fa2256ee3469-4000x2970.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1188&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Now, two months later, our office has finally come together. It wasn’t easy — we’ve been buried under piles of empty cardboard boxes, furniture assembly instructions, and shipping confirmation emails for the last couple of months — but we made it out alive on the other side with an office we love. The space, much like our apps, will never feel quite “done” as it continues to grow and change with the company, but that’s the beauty of moving into an office that’s truly our own and allows us to evolve beyond what we are right now.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9e4b53e322003cea86a51efcd0d5ca460800cfc5-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/9e4b53e322003cea86a51efcd0d5ca460800cfc5-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/9e4b53e322003cea86a51efcd0d5ca460800cfc5-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/9e4b53e322003cea86a51efcd0d5ca460800cfc5-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/9e4b53e322003cea86a51efcd0d5ca460800cfc5-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/9e4b53e322003cea86a51efcd0d5ca460800cfc5-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/9e4b53e322003cea86a51efcd0d5ca460800cfc5-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/9e4b53e322003cea86a51efcd0d5ca460800cfc5-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9e4b53e322003cea86a51efcd0d5ca460800cfc5-4000x2666.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1066&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;As nervous as we were to take this leap, leaving that small, seven-person room behind was a big accomplishment for us. We needed room to grow and shape our company’s identity, and this new office gives us that. We’re excited to share the beginning of a new era of Lickability with you.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Want to stop by? Send us an &lt;a href=&quot;mailto:hello@lickability.com&quot;&gt;email&lt;/a&gt; and let us know! And if this looks like an office you’d want to work in, you’re in luck: &lt;strong&gt;&lt;a href=&quot;https://lickability.com/careers&quot;&gt;we’re hiring&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Jillian Meehan</author></item><item><title>Working with Clutch</title><link>https://lickability.com/blog/working-with-clutch/</link><guid isPermaLink="true">https://lickability.com/blog/working-with-clutch/</guid><description>The reviews are in…</description><pubDate>Thu, 18 Oct 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Big news: Clutch has featured Lickability as one of the best app development studios in the US! 🙌 🎉 ✨&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://clutch.co/app-developers/nyc&quot;&gt;Clutch&lt;/a&gt; is a platform for unbiased reviews of B2B businesses (like Yelp for app development companies), which we use to give a way for our clients to provide honest feedback about what it’s like to work with us. We’ve thoroughly enjoyed working with Clutch, and they’ve featured us as one of the top businesses using their service for several years now. This year, they released their first-ever report naming the top mobile app developers in every state — and we’re so proud to be included as one of the top companies in New York, alongside studios like Postlight, Stride, WillowTree, Prolific Interactive, and more.&lt;/p&gt;&lt;p&gt;So, what makes Lickability one of the top app development companies in 2018? We’ll let our &lt;a href=&quot;https://lickability.com/clients&quot;&gt;client work&lt;/a&gt; speak for itself. What we love about Clutch is that they speak to our clients directly to curate quality, helpful reviews that give the most accurate insight possible into what it’s like to work with us. We’ve received truly great feedback this way, and we couldn’t be happier:&lt;/p&gt;&lt;blockquote class=&quot;quote-block&quot;&gt; &lt;p class=&quot;quote-text&quot;&gt;Their business is a lifestyle for them, so they’re able to contract really talented people.&lt;/p&gt; &lt;cite class=&quot;quote-author&quot;&gt;COO, Mobile App Company&lt;/cite&gt; &lt;/blockquote&gt;&lt;blockquote class=&quot;quote-block&quot;&gt; &lt;p class=&quot;quote-text&quot;&gt;They delivered on time, offered proactive suggestions for improvement, and took pride in their work.&lt;/p&gt; &lt;cite class=&quot;quote-author&quot;&gt;Executive Director &amp;amp; Product Design Lead, The Atlantic&lt;/cite&gt; &lt;/blockquote&gt;&lt;blockquote class=&quot;quote-block&quot;&gt; &lt;p class=&quot;quote-text&quot;&gt;[Lickability]’s place among iOS developers in the world is really high. They are like the rock stars of the iOS scene.&lt;/p&gt; &lt;cite class=&quot;quote-author&quot;&gt;Product Director, Meetup&lt;/cite&gt; &lt;/blockquote&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f6beb7da4e5b980dac31c12879be932314c735a2-555x600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/f6beb7da4e5b980dac31c12879be932314c735a2-555x600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/f6beb7da4e5b980dac31c12879be932314c735a2-555x600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=555 555w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f6beb7da4e5b980dac31c12879be932314c735a2-555x600.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400&quot; width=&quot;400&quot; height=&quot;432&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Being identified as a leader in our industry is a huge honor, and we’re incredibly thankful to be recognized for our work — but it’s all thanks to our fantastic clients. We owe a huge thank you to everyone who has helped support the Lickability team.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;To learn more about what we do and why we’ve been featured by Clutch, check out the reviews on our &lt;a href=&quot;https://clutch.co/profile/lickability&quot;&gt;profile&lt;/a&gt;. (And if you like what you read, &lt;a href=&quot;https://lickability.com/contact&quot;&gt;hire us&lt;/a&gt;?)&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Team Lickability</author></item><item><title>The Value of Values</title><link>https://lickability.com/blog/the-value-of-values/</link><guid isPermaLink="true">https://lickability.com/blog/the-value-of-values/</guid><description>Write down what matters</description><pubDate>Sun, 14 Oct 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When we started Lickability in 2009, we didn’t dream of writing mission statements or defining &lt;em&gt;corporate values&lt;/em&gt;. We were a couple of high-schoolers in our parents’ houses, working on &lt;a href=&quot;https://lickability.com/products&quot;&gt;apps that we wanted to see&lt;/a&gt; on the App Store—and later, working on even more apps from our respective dorm rooms.&lt;/p&gt;&lt;p&gt;Lickability &lt;a href=&quot;https://twitter.com/lickability/status/1916562199&quot;&gt;started as a side-project&lt;/a&gt; and has grown into a larger, more serious operation. Before we even hired our first employee, we realized that it might be a good idea to define what we actually value. How had we worked together over the last few years, and what did we want new folks joining our little team to carry forward?&lt;/p&gt;&lt;p&gt;Lots of corporate values we’d read were highfalutin and impractical. We wanted a document that we could actually use. Here are the values we came up with three years ago when we turned our passion into our full-time jobs that we still work by today. They’re short. They’re simple. And most importantly, we mean them.&lt;/p&gt;&lt;h3&gt;Our Values (a.k.a. The &lt;a href=&quot;https://twitter.com/horse_ios/status/424252170267947008&quot;&gt;&lt;strong&gt;Five Flavors&lt;/strong&gt;&lt;/a&gt; of Lickability™)&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b66c542b639af67ee4864d12ca221e3816ad5e4c-4000x5996.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/b66c542b639af67ee4864d12ca221e3816ad5e4c-4000x5996.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/b66c542b639af67ee4864d12ca221e3816ad5e4c-4000x5996.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/b66c542b639af67ee4864d12ca221e3816ad5e4c-4000x5996.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/b66c542b639af67ee4864d12ca221e3816ad5e4c-4000x5996.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/b66c542b639af67ee4864d12ca221e3816ad5e4c-4000x5996.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/b66c542b639af67ee4864d12ca221e3816ad5e4c-4000x5996.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/b66c542b639af67ee4864d12ca221e3816ad5e4c-4000x5996.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b66c542b639af67ee4864d12ca221e3816ad5e4c-4000x5996.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;2398&quot;/&gt;  &lt;/figure&gt; &lt;h4&gt;◾️ &lt;strong&gt;Simplicity&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;&lt;em&gt;Make everything as simple as possible, but not simpler. Inessential complexity is bad design.&lt;/em&gt;&lt;/p&gt;&lt;h4&gt;🛠 &lt;strong&gt;Craft&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;&lt;em&gt;Pay attention to every detail. Take extra care even when it goes unnoticed.&lt;/em&gt;&lt;/p&gt;&lt;h4&gt;🔎 &lt;strong&gt;Clarity&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;&lt;em&gt;Build and explain systems as explicitly as possible. Avoid cleverness.&lt;/em&gt;&lt;/p&gt;&lt;h4&gt;🙌 &lt;strong&gt;Collaboration&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;&lt;em&gt;Ask for help and offer it. We’re better when we work together.&lt;/em&gt;&lt;/p&gt;&lt;h4&gt;😄 &lt;strong&gt;Delight&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;&lt;em&gt;Have fun. Make things that will help people be happier.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;What do these mean in practice? An office full of whiteboards and pair programming sessions (especially when an &lt;a href=&quot;https://twitter.com/mliberatore/status/1039928323219091465&quot;&gt;engineer breaks their arm&lt;/a&gt;). Pitching in when your coworker asks you for help fixing a bug, building a desk, or breaking down cardboard boxes. Aiming for fun and delight in &lt;a href=&quot;https://lickability.com/products&quot;&gt;our products&lt;/a&gt; (like the sparkly animation when you buy colors in Pinpoint) and in &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;our communication&lt;/a&gt; whenever it makes sense. &lt;a href=&quot;https://blog.lickability.com/slack-is-for-friends-too-57ab3f9d9da0?source=collection_home---4------4---------------------&quot;&gt;Inviting people outside&lt;/a&gt; the team into our spaces for more perspective. &lt;a href=&quot;https://thenextweb.com/apple/2011/10/24/steve-jobs-obsession-with-the-quality-of-the-things-unseen/&quot;&gt;Painting the back of the fence&lt;/a&gt;. Engineering our internal processes and code to be as simple and painless to work within as possible, and when we find something that can be better, like &lt;a href=&quot;https://blog.lickability.com/adding-a-401-k-for-our-small-business-wasnt-scary-61920583c90f&quot;&gt;our 401(k) plan&lt;/a&gt;, putting in the effort to fix it.&lt;/p&gt;&lt;p&gt;Since we’ve codified these principles, the company has felt more unified in our direction and it’s a lot easier to break the occasional tie when we’re unsure of what to do next. We aim to stay in business for a long time to continue serving our clients and customers. As such, these values have to remain open to change with their needs and our experiences. Like almost everything we work on, I’m sure there’ll be a v2.0.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;We’re always interested in what other folks and companies intentionally value in their work. &lt;a href=&quot;https://twitter.com/lickability#tweet&quot;&gt;&lt;strong&gt;Tell us about &lt;em&gt;your&lt;/em&gt; values, either personal or professional&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>mb bischoff</author></item><item><title>Home Sweet Home (Screen)</title><link>https://lickability.com/blog/home-sweet-home-screen/</link><guid isPermaLink="true">https://lickability.com/blog/home-sweet-home-screen/</guid><description>Swipe to unlock the apps we can’t live without</description><pubDate>Thu, 27 Sep 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Everyone’s home screen is different. Some have lots of apps covering the screen, some have neatly-organized folders, some are color-coordinated, some are nearly empty, and some are downright chaotic. The apps you choose to put on your home screen, and the order you choose to put them in, says a lot about you.&lt;/p&gt;&lt;p&gt;So what do our iPhone home screens at Lickability say about us? Take a look below and decide for yourself.&lt;/p&gt;&lt;h3&gt;Jillian Meehan&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/8e2dcd311f2435504da5dce302efd5ad47c6a973-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/8e2dcd311f2435504da5dce302efd5ad47c6a973-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/8e2dcd311f2435504da5dce302efd5ad47c6a973-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/8e2dcd311f2435504da5dce302efd5ad47c6a973-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/8e2dcd311f2435504da5dce302efd5ad47c6a973-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;2436&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/215dde523a25345c84cae679e5ce455ad39e95e1-1125x2436.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/215dde523a25345c84cae679e5ce455ad39e95e1-1125x2436.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/215dde523a25345c84cae679e5ce455ad39e95e1-1125x2436.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/215dde523a25345c84cae679e5ce455ad39e95e1-1125x2436.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/215dde523a25345c84cae679e5ce455ad39e95e1-1125x2436.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;2436&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Let me say this right off the bat: I have an intense need to keep all of my apps on one screen. If I &lt;em&gt;ever&lt;/em&gt; have a second page of apps, it’s because I’m trying a few out before I decide to fully commit and give them a coveted home screen spot.&lt;/p&gt;&lt;p&gt;I refuse to cover my entire screen with apps because I hate the way it looks, so aside from my dock and my first three rows of can’t-live-without apps (&lt;a href=&quot;https://www.instagram.com/jilliangmeehan/&quot;&gt;Instagram&lt;/a&gt;, &lt;a href=&quot;https://www.omnigroup.com/omnifocus/&quot;&gt;OmniFocus&lt;/a&gt;, &lt;a href=&quot;https://bear.app&quot;&gt;Bear&lt;/a&gt;, &lt;a href=&quot;https://getdrafts.com&quot;&gt;Drafts&lt;/a&gt;, etc.), I’m a big fan of shoving everything into folders that only make sense to me. The best way I can explain my folder system is that my most-used apps live on the first page of each folder, varying in importance from left to right on the screen.&lt;/p&gt;&lt;p&gt;My ⭐️ folder has some of my most important apps, like &lt;a href=&quot;https://citymapper.com/&quot;&gt;Citymapper&lt;/a&gt;, &lt;a href=&quot;https://classpass.com&quot;&gt;ClassPass&lt;/a&gt;, &lt;a href=&quot;https://slack.com&quot;&gt;Slack&lt;/a&gt;, and my &lt;a href=&quot;https://www.simple.com&quot;&gt;Simple&lt;/a&gt; banking app — all my money-related apps, like &lt;a href=&quot;https://venmo.com&quot;&gt;Venmo&lt;/a&gt;, &lt;a href=&quot;https://claritymoney.com&quot;&gt;Clarity&lt;/a&gt;, and &lt;a href=&quot;https://digit.co&quot;&gt;Digit&lt;/a&gt;, live on the second page of this folder.&lt;/p&gt;&lt;p&gt;The 🌈 folder has a lot of productivity-related apps, like &lt;a href=&quot;https://1password.com&quot;&gt;1Password&lt;/a&gt;, &lt;a href=&quot;https://www.notion.so&quot;&gt;Notion&lt;/a&gt;, and &lt;a href=&quot;https://streaksapp.com&quot;&gt;Streaks&lt;/a&gt;. The second page of this folder has some more of my “fun” apps, like &lt;a href=&quot;https://www.costarastrology.com&quot;&gt;Co-Star&lt;/a&gt;, &lt;a href=&quot;https://itunes.apple.com/us/app/supergreat/id1360338670?mt=8&quot;&gt;Supergreat&lt;/a&gt;, and &lt;a href=&quot;https://giphy.com&quot;&gt;Giphy&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;My 👅 folder has all of the apps we use or work on at Lickability, and my 🍎 folder has, naturally, a bunch of Apple’s pre-installed apps.&lt;/p&gt;&lt;h3&gt;mb Bischoff&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a1176cc5f103f5466f71b9d5137a4942f65313bb-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/a1176cc5f103f5466f71b9d5137a4942f65313bb-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/a1176cc5f103f5466f71b9d5137a4942f65313bb-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/a1176cc5f103f5466f71b9d5137a4942f65313bb-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a1176cc5f103f5466f71b9d5137a4942f65313bb-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;2436&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/03c29844302ebcfdc9876cb0c675aa9b2822fb91-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/03c29844302ebcfdc9876cb0c675aa9b2822fb91-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/03c29844302ebcfdc9876cb0c675aa9b2822fb91-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/03c29844302ebcfdc9876cb0c675aa9b2822fb91-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/03c29844302ebcfdc9876cb0c675aa9b2822fb91-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;2436&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;As you can maybe tell, I’m a &lt;em&gt;little&lt;/em&gt; obsessive and I like things to be clear and well-organized. My home screen is a reflection of my personality and the apps I use every day to do my work and enjoy my life.&lt;/p&gt;&lt;p&gt;I wouldn’t be able to do my work without Messages, &lt;a href=&quot;https://slack.com&quot;&gt;Slack&lt;/a&gt;, &lt;a href=&quot;https://flexibits.com/fantastical-iphone&quot;&gt;Fantastical&lt;/a&gt;, and &lt;a href=&quot;https://sparkmailapp.com&quot;&gt;Spark&lt;/a&gt; to communicate with coworkers and contacts. I take tons of photos (read: selfies), so Camera and Photos can never be too accessible. &lt;a href=&quot;https://bear.app&quot;&gt;Bear&lt;/a&gt; and &lt;a href=&quot;https://dayoneapp.com/&quot;&gt;Day One&lt;/a&gt; are where I take notes and reflect. &lt;a href=&quot;https://www.omnigroup.com/omnifocus/&quot;&gt;OmniFocus&lt;/a&gt; and &lt;a href=&quot;https://getdrafts.com&quot;&gt;Drafts&lt;/a&gt; are where I get shit done. And Safari, News, Music, &lt;a href=&quot;https://twitter.com/mb/&quot;&gt;Twitter&lt;/a&gt;, &lt;a href=&quot;https://overcast.fm&quot;&gt;Overcast&lt;/a&gt;, &lt;a href=&quot;https://www.amazon.com/kindle-dbs/fd/kcp&quot;&gt;Kindle&lt;/a&gt;, &lt;a href=&quot;https://itunes.apple.com/us/app/wikipedia/id324715238?mt=8&quot;&gt;Wikpedia&lt;/a&gt;, &lt;a href=&quot;https://www.instapaper.com/&quot;&gt;Instapaper&lt;/a&gt; and &lt;a href=&quot;https://www.reederapp.com&quot;&gt;Reeder&lt;/a&gt; are where I relax and learn new things. Finally, I want easy access to all the other apps we make and use at Lickability. 👅&lt;/p&gt;&lt;p&gt;Some honorable mentions: I’m bad at sleeping enough, so &lt;a href=&quot;https://streaksapp.com&quot;&gt;Streaks&lt;/a&gt; tries to help me with that, and &lt;a href=&quot;https://getmagic.com&quot;&gt;Magic&lt;/a&gt; is for those times when I’m dreading doing something but I know it has to get done anyway.&lt;/p&gt;&lt;h3&gt;Michael Liberatore&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/1c0b9cf50f27e8b8a36eec5391743fe13c17697a-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/1c0b9cf50f27e8b8a36eec5391743fe13c17697a-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/1c0b9cf50f27e8b8a36eec5391743fe13c17697a-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/1c0b9cf50f27e8b8a36eec5391743fe13c17697a-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/1c0b9cf50f27e8b8a36eec5391743fe13c17697a-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;2436&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/67f4aa6f3b5d61221c44c9babed49e901b15964b-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/67f4aa6f3b5d61221c44c9babed49e901b15964b-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/67f4aa6f3b5d61221c44c9babed49e901b15964b-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/67f4aa6f3b5d61221c44c9babed49e901b15964b-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/67f4aa6f3b5d61221c44c9babed49e901b15964b-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;2436&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Since Spotlight search is incredibly quick to find the exact app you’re looking for, I’ve been far less precious about the organization of my apps in general. That aside, the apps that comprise my home screen loosely fall into three categories.&lt;/p&gt;&lt;p&gt;The first category, which accounts for most of my home screen (literally the top five rows and the dock) includes the apps I use multiple times a day. Additionally, for Messages, &lt;a href=&quot;https://sparkmailapp.com&quot;&gt;Spark&lt;/a&gt;, &lt;a href=&quot;https://slack.com&quot;&gt;Slack&lt;/a&gt;, &lt;a href=&quot;https://culturedcode.com/things/&quot;&gt;Things&lt;/a&gt;, and my most used social apps, I want to easily see my notification badges, so I like having them visible every time I unlock my phone instead of tucking them away in the unorganized basement storage closet that is screens 2–5.&lt;/p&gt;&lt;p&gt;The second and third categories change frequently. Currently, &lt;a href=&quot;https://holedown.com&quot;&gt;holedown&lt;/a&gt; fills category two: the games I’m playing right now. Games have a fairly short life span on my phone, and if I don’t see it on screen #1, I’ll quickly forget about it. The third category includes apps I’m checking out at the moment, aspire to use more, or have just downloaded and don’t want to forget to try. At the moment, &lt;a href=&quot;https://itunes.apple.com/us/app/shortcuts/id915249334?mt=8&quot;&gt;Shortcuts&lt;/a&gt; is the only app in this category. I really aspire to use it more to get hooked on iOS automation.&lt;/p&gt;&lt;h3&gt;Grant Butler&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/2c0c7a9c855df8334bc8570cd529fb1e70fa487e-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/2c0c7a9c855df8334bc8570cd529fb1e70fa487e-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/2c0c7a9c855df8334bc8570cd529fb1e70fa487e-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/2c0c7a9c855df8334bc8570cd529fb1e70fa487e-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/2c0c7a9c855df8334bc8570cd529fb1e70fa487e-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;2436&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/09682ff3157f7aecdbdba35585729b22446087d7-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/09682ff3157f7aecdbdba35585729b22446087d7-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/09682ff3157f7aecdbdba35585729b22446087d7-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/09682ff3157f7aecdbdba35585729b22446087d7-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/09682ff3157f7aecdbdba35585729b22446087d7-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;2436&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;The apps on my home screen are usually the ones that I use on a regular basis, with the ones in my dock being the ones I use the most. Apart from those, &lt;a href=&quot;http://Slack&quot;&gt;Slack&lt;/a&gt; gets a lot of use for work, and &lt;a href=&quot;https://discordapp.com&quot;&gt;Discord&lt;/a&gt; for talking with friends. &lt;a href=&quot;https://trello.com&quot;&gt;Trello&lt;/a&gt; and &lt;a href=&quot;https://airtable.com&quot;&gt;Airtable&lt;/a&gt; are both used for convention planning and management, so having them on my home screen is nice for easy access to that information, especially when it’s the weekend of the event.&lt;/p&gt;&lt;p&gt;Some of the apps on my home screen are aspirational—apps that I want to start using on a regular basis. I’ve got &lt;a href=&quot;https://claritymoney.com&quot;&gt;Clarity Mone&lt;/a&gt;y and &lt;a href=&quot;https://recaf.app&quot;&gt;RECaf&lt;/a&gt;, in hopes that by having them on my home screen, I’ll use them often (it’s not really working so far). And &lt;a href=&quot;https://itunes.apple.com/us/app/shortcuts/id915249334?mt=8&quot;&gt;Shortcuts&lt;/a&gt; is there to prompt me to experiment with it and try to find ways I can integrate it into my regular usage.&lt;/p&gt;&lt;p&gt;I’ve got a folder for games on a separate screen, but there are two that I keep on my homescreen: &lt;a href=&quot;https://itunes.apple.com/us/app/new-york-times-crossword/id307569751?mt=8&quot;&gt;Crossword&lt;/a&gt; and &lt;a href=&quot;https://www.nytimes.com/puzzles/spelling-bee&quot;&gt;Spelling Bee&lt;/a&gt;, both from The New York Times. While I’m not very good at either, I still enjoy the challenge.&lt;/p&gt;&lt;h3&gt;Brian Capps&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/80365416938877677bc398a357b11588765069ef-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/80365416938877677bc398a357b11588765069ef-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/80365416938877677bc398a357b11588765069ef-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/80365416938877677bc398a357b11588765069ef-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/80365416938877677bc398a357b11588765069ef-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;2436&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/29e48961773c96777ff7a82ee7de2b1b7c398640-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/29e48961773c96777ff7a82ee7de2b1b7c398640-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/29e48961773c96777ff7a82ee7de2b1b7c398640-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/29e48961773c96777ff7a82ee7de2b1b7c398640-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/29e48961773c96777ff7a82ee7de2b1b7c398640-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;2436&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Here’s what Brian had to say about his home screen setup:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/ec94eb7bd692c597bf66c42234f6e64014256225-1125x459.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/ec94eb7bd692c597bf66c42234f6e64014256225-1125x459.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/ec94eb7bd692c597bf66c42234f6e64014256225-1125x459.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/ec94eb7bd692c597bf66c42234f6e64014256225-1125x459.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/ec94eb7bd692c597bf66c42234f6e64014256225-1125x459.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;459&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;(Let it be known that Jillian has never once “searched” “for” “an” “app.”) &lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;Michael Amundsen&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9ad537cdcb050d2d5908d748edc878ebb2ab7ff8-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/9ad537cdcb050d2d5908d748edc878ebb2ab7ff8-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/9ad537cdcb050d2d5908d748edc878ebb2ab7ff8-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/9ad537cdcb050d2d5908d748edc878ebb2ab7ff8-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9ad537cdcb050d2d5908d748edc878ebb2ab7ff8-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;2436&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/663fef63d2fa43646c73ac9785330653532a45e4-1125x2436.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/663fef63d2fa43646c73ac9785330653532a45e4-1125x2436.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/663fef63d2fa43646c73ac9785330653532a45e4-1125x2436.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/663fef63d2fa43646c73ac9785330653532a45e4-1125x2436.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/663fef63d2fa43646c73ac9785330653532a45e4-1125x2436.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;2436&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;I have a &lt;a href=&quot;http://www.idownloadblog.com/tag/wallpapers-of-the-week/&quot;&gt;wallpaper e-mail&lt;/a&gt; I get each Sunday that I update my wallpaper to. This week was green themed.&lt;/p&gt;&lt;p&gt;Generally, my home page has the apps I’m most actively using. Having said that, these days I just swipe down and search for the app I need, because most of the time, either Siri suggests what I’m looking for, or it’s faster to search&lt;/p&gt;&lt;p&gt;It’s kind of interesting that, if I think about it, my primary way of interacting with my phone or computer is to use Spotlight to search and open what I need.&lt;/p&gt;&lt;h3&gt;Andrew Harrison&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/4e7d3358fec81342e10c39ade37b80abee826ae1-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/4e7d3358fec81342e10c39ade37b80abee826ae1-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/4e7d3358fec81342e10c39ade37b80abee826ae1-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/4e7d3358fec81342e10c39ade37b80abee826ae1-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/4e7d3358fec81342e10c39ade37b80abee826ae1-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;2436&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e42ac27894f7a243bf631c2032499b8d8b3d4a1a-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/e42ac27894f7a243bf631c2032499b8d8b3d4a1a-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/e42ac27894f7a243bf631c2032499b8d8b3d4a1a-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/e42ac27894f7a243bf631c2032499b8d8b3d4a1a-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e42ac27894f7a243bf631c2032499b8d8b3d4a1a-1125x2436.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;2436&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;No comment. 😎&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Adding a 401(k) for Our Small Business Wasn’t Scary</title><link>https://lickability.com/blog/adding-a-401-k-for-our-small-business-wasn-t-scary/</link><guid isPermaLink="true">https://lickability.com/blog/adding-a-401-k-for-our-small-business-wasn-t-scary/</guid><description>…the second time we did it.</description><pubDate>Thu, 20 Sep 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;401(k)s are hopelessly complicated. At least, as a small business owner looking to add retirement benefits for our employees, that’s how it seemed earlier this year. The retirement market is flooded with indecipherable jargon, deceptive fees, and atrocious software, and the search can seem overwhelming and pointless. However, after hours upon hours of research, we broke the problem down into understandable categories that could apply to any small business, and we could not be happier with our new provider.&lt;/p&gt;&lt;h3&gt;What Even Is a 401(k)?&lt;/h3&gt;&lt;p&gt;Before diving into the details of our search, it’s important to have a quick refresher on what a 401(k) even is. Put simply, it’s a tax-advantaged retirement plan offered by employers in the U.S., subject to special restrictions and regulations.&lt;/p&gt;&lt;p&gt;Employees can make contributions pre-tax from their paycheck, paying taxes when withdrawing for retirement, or post-tax, withdrawing tax-free at retirement. The employee contributions are limited to $18,500 per year combined in 2018, and there’s a penalty for early withdrawal before retirement. There’s plenty more nuance, but starting from this baseline, it became a lot easier for us to cut through confusing terminology and rules and begin thinking about what separates a great 401(k) plan from a mediocre one.&lt;/p&gt;&lt;h3&gt;The Sound of Settling&lt;/h3&gt;&lt;p&gt;It’s a lot easier to articulate what would constitute a desirable retirement plan and provider once you realize that you’ve got a poor one. Wanting to become competitive on employee compensation with our larger competitors, and hoping to make life better for our employees in the process, we added a 401(k) plan at the beginning of 2017. Our payroll provider, &lt;a href=&quot;https://justworks.com&quot;&gt;Justworks&lt;/a&gt; (which we absolutely love), partners with a company called Slavic401k to offer plans fully integrated with payroll, so we took the path of least resistance and signed up for it, excited for our new financial future.&lt;/p&gt;&lt;p&gt;The warning signs came on our first paycheck contribution. I checked the numbers and immediately discovered that half the company had incorrect contributions. After Slavic corrected their error, made more, and then corrected them &lt;em&gt;again&lt;/em&gt;, we began to suspect that this wasn’t a fluke. More than that, the website was barely usable, unable to easily show simple calculations like overall rate of return or total employee contributions. Finally, when it took 8 months to fully correct a mistake affecting one of our employees—to the tune of nearly $1000—we knew it was past time to switch.&lt;/p&gt;&lt;h3&gt;Breaking It Down&lt;/h3&gt;&lt;p&gt;When running a business, there is no shortage of large, daunting tasks that seem insurmountable, and breaking them down into achievable parts is a survival skill that served us particularly well when looking for a new 401(k) provider. We wanted a partner who had our employees’ best interests in mind and who made all participants feel confident that they knew how to achieve their financial retirement goals. Here’s the way we categorized those desires into measurable groups:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Fees&lt;/strong&gt;: The average 401(k) fees for small plans are &lt;a href=&quot;https://www.marketwatch.com/story/dear-adviser-401k-plans-are-too-costly-2017-08-24&quot;&gt;between 1.5% and 2%&lt;/a&gt; of assets. Our provider had total fees for participants of about 1%, which was considered relatively “low.” However, even a 1% fee can have significant &lt;a href=&quot;https://www.nerdwallet.com/blog/investing/millennial-retirement-fees-one-percent-half-million-savings-impact/&quot;&gt;effects&lt;/a&gt; in the long run, so we wanted to do better. There are potential expenses from providers, advisors, and the investments themselves, but any plan we chose had to have combined fees much lower than 1% for a company our size. This was the most important priority.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Interface&lt;/strong&gt;: Saving for retirement is chiefly a psychological endeavor, a pattern of beneficial behaviors that can be reinforced and aided by software. For this reason, software quality was an important factor in our search. We wanted the interface to provide a glanceable overview of how much money is in the account, where it came from, and what returns the investments have earned. Operations like changing contribution amounts or portfolio allocations had to be simple and understandable for those without a degree in finance, in order to encourage greater participation and contributions with a lower barrier to entry.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Fiduciary&lt;/strong&gt;: It almost goes without saying, but most financial companies are focused solely on enriching their shareholders and do not give one shit about their customers. The only way to &lt;em&gt;try&lt;/em&gt; to ensure that one considers your interests is when they have a legal obligation to do so. A law called the &lt;a href=&quot;https://www.dol.gov/general/topic/retirement/erisa&quot;&gt;ERISA&lt;/a&gt; defines several types of opt-in fiduciary responsibilities that require advisors to act in a client’s interest in various ways. The highest standard of care is a &lt;a href=&quot;https://blog.healthequity.com/3-38-investment-manager&quot;&gt;3(38) investment manager&lt;/a&gt; fiduciary, who must act in the client’s best interest on many levels, including choosing and monitoring investments and keeping fees reasonable. We wanted to work with a company who was willing to adhere to these stricter standards and liability under the law.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Reviews&lt;/strong&gt;: This one is pretty self-explanatory. If customers wrote a bunch of negative reviews about the company, that wasn’t a good sign.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Customer Support&lt;/strong&gt;: I once had a Vanguard customer service employee laugh at me for asking a stupid question over the phone. This stuff can be confusing sometimes, and no one deserves that, so the bar here was “likely won’t laugh at those asking beginner questions.”&lt;/p&gt;&lt;h4&gt;The Results&lt;/h4&gt;&lt;p&gt;After establishing the rubric for evaluating our options, we got to work comparing the candidates listed as the “top” or “best” for small businesses according to Google searches. The results for these large to medium players in the industry were abjectly abysmal, as you’ll see below.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/2c8fa34ba9cdb70730304295dc257151f657e6a0-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/2c8fa34ba9cdb70730304295dc257151f657e6a0-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/2c8fa34ba9cdb70730304295dc257151f657e6a0-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/2c8fa34ba9cdb70730304295dc257151f657e6a0-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/2c8fa34ba9cdb70730304295dc257151f657e6a0-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/2c8fa34ba9cdb70730304295dc257151f657e6a0-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/2c8fa34ba9cdb70730304295dc257151f657e6a0-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2560 2560w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/2c8fa34ba9cdb70730304295dc257151f657e6a0-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;900&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Yikes. Our investigation showed a complacent 401(k) industry full of inferior products. Luckily, we’d expanded the breadth of our search to include firms with hundreds of millions of dollars under management and fresher approaches, not just the trillions and billions held at the stale companies shown above.&lt;/p&gt;&lt;p&gt;While we didn’t have great results with some of the most established companies, there were three more customer-focused ones that really stood out. &lt;a href=&quot;https://humaninterest.com&quot;&gt;Human Interest&lt;/a&gt;, an oddly named robo-advisor, had decent &lt;a href=&quot;https://humaninterest.com/pricing/&quot;&gt;fees&lt;/a&gt; (about 0.58% for participants) and helpful, knowledgable employees. &lt;a href=&quot;https://business.betterment.com&quot;&gt;Betterment for Business&lt;/a&gt;, the 401(k) offering of personal investment robo-advisor Betterment, provided plans with reasonable, transparently low &lt;a href=&quot;https://business.betterment.com/pricing/&quot;&gt;fees&lt;/a&gt; (about 0.35% for participants), best-in-class software, and solid reviews. And finally there was &lt;a href=&quot;https://www.guideline.com&quot;&gt;Guideline&lt;/a&gt;, a newer entrant that the founder of TaskRabbit started in 2015, which had absurdly low &lt;a href=&quot;https://www.guideline.com/pricing&quot;&gt;fees&lt;/a&gt; (about 0.06% for participants), useful and pretty software, and nearly limitless payroll integrations. Each one of these companies would take on 3(38) fiduciary responsibility with no added fees and had some great reviews, and I’m confident that any one of them would have worked well at our business — there could, however, be only one winner.&lt;/p&gt;&lt;h3&gt;The Winner&lt;/h3&gt;&lt;p&gt;The clear choice for us, and hopefully for many others, was Guideline. Their fees of only $8 per employee per month for the business and picks of low-cost investments with average fees of only 0.06% for participants are shockingly reasonable, putting the rest of their competitors to shame (and no, this is &lt;em&gt;not&lt;/em&gt; a sponsored post!). They make it easy to choose a portfolio by asking participants about their risk tolerance and recommending a plan, while offering an extensive fund menu for more advanced users who would like to make their own. And, as if that weren’t enough, their software is beautiful and functional, and their representatives are helpful and professional.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/cc96c421f8e12977d39a9ca7e860935af215d252-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/cc96c421f8e12977d39a9ca7e860935af215d252-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/cc96c421f8e12977d39a9ca7e860935af215d252-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/cc96c421f8e12977d39a9ca7e860935af215d252-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/cc96c421f8e12977d39a9ca7e860935af215d252-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/cc96c421f8e12977d39a9ca7e860935af215d252-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/cc96c421f8e12977d39a9ca7e860935af215d252-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2560 2560w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/cc96c421f8e12977d39a9ca7e860935af215d252-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;900&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/49317de0197f66d9ba46450e736d6163165eddea-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/49317de0197f66d9ba46450e736d6163165eddea-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/49317de0197f66d9ba46450e736d6163165eddea-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/49317de0197f66d9ba46450e736d6163165eddea-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/49317de0197f66d9ba46450e736d6163165eddea-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/49317de0197f66d9ba46450e736d6163165eddea-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/49317de0197f66d9ba46450e736d6163165eddea-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2560 2560w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/49317de0197f66d9ba46450e736d6163165eddea-2560x1440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;900&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Here are the Slavic and Guideline slides from my very professional presentation on switching&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Although adding a 401(k) or changing your provider can seem intimidating, it’s simpler than it seems, and can make a real difference in your employees’ lives. If you run a small business, I would highly recommend spending some time to pick the right option and discuss it with your team. Hopefully, our experience switching providers can yield some useful insight into how to best evaluate your choices when you do. It may be scary, but the only thing you truly need to fear is disappointing your employees by not offering any retirement plan at all. And spiders. Spiders are terrifying.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Disclaimer: I’m not a licensed financial advisor and this is just like, my opinion, man. None of the links here are referrals—however, if you’d like a referral link to Guideline, &lt;a href=&quot;https://www.guideline.com/invite/vznQhcG3&quot;&gt;here you go&lt;/a&gt;. We both get $250 towards their services if you use it.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Brian Capps</author></item><item><title>Conferences We Know and Love</title><link>https://lickability.com/blog/conferences-we-know-and-love/</link><guid isPermaLink="true">https://lickability.com/blog/conferences-we-know-and-love/</guid><description>+ why you should love them too</description><pubDate>Thu, 13 Sep 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We’re big fans of conferences. Why? Because they give you the opportunity to step away from your desk, expose yourself to new ideas and practices, and meet lots of cool, interesting people in your community.&lt;/p&gt;&lt;p&gt;Conferences are extremely valuable — if you pick the right ones to go to. But maybe you’re not sure where to start. Or maybe you’re curious about a particular conference, but want to hear from someone else about what it’s like before you commit to buying a ticket.&lt;/p&gt;&lt;p&gt;We’ve got you covered. Here’s a look at some of the conferences we love attending. Some of these conferences are no longer around (r.i.p.) but most of them are still going strong. Maybe we’ll see you at one of them next year. 😉&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://www.tryswift.co/events/2018/nyc/&quot;&gt;try! Swift&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;em&gt;New York, New York&lt;/em&gt;&lt;/p&gt;&lt;p&gt;try! Swift is a really good conference for the community in NYC. It’s great for networking, exposure, and talking to people. I find the most value in the community-building part. Oftentimes, the office hours to talk to the speaker after their talk is the most useful thing.&lt;/p&gt;&lt;p&gt;– Michael Amundsen&lt;/p&gt;&lt;p&gt;&lt;strong&gt;You might like try! Swift if:&lt;/strong&gt; you’re a developer at any level (beginners included!) in New York City looking for pals and mentors as you learn more about Swift.&lt;/p&gt;&lt;blockquote&gt;Sponsoring a diversity scholarship at &lt;a href=&quot;https://twitter.com/tryswiftnyc?ref_src=twsrc%5Etfw&quot;&gt;@tryswiftnyc&lt;/a&gt; is one of the easiest decisions we make every year. &lt;a href=&quot;https://twitter.com/NatashaTheRobot?ref_src=twsrc%5Etfw&quot;&gt;@NatashaTheRobot&lt;/a&gt; and co. do an incredible job organizing, and it’s such a clear way to promote a more inclusive iOS community in our city. &lt;a href=&quot;https://t.co/ov17FHbzUt&quot;&gt;https://t.co/ov17FHbzUt&lt;/a&gt;&lt;br&gt;&lt;br&gt;— mb Bischoff (@mb) &lt;a href=&quot;https://twitter.com/mb/status/1036995455656312832?ref_src=twsrc%5Etfw&quot;&gt;September 4, 2018&lt;/a&gt;&lt;/blockquote&gt;&lt;h3&gt;&lt;a href=&quot;https://layers.is&quot;&gt;Layers&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;em&gt;Wherever WWDC is&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Having never been to &lt;a href=&quot;https://blog.lickability.com/wwdc-and-layers-2018-eae74f04f5e6&quot;&gt;WWDC&lt;/a&gt; in my life, I feel that I can confidently say that Layers is the better conference of the two. For the unfamiliar, Layers is a three-day design conference that takes place in the same city and during the same week as Apple’s conference. It is (as you would expect) an incredibly well-designed event, with fantastic speakers, great snacks, and a pretty bomb party at the end of the week. My favorite part is the size—there are plenty of awesome people to meet, but it’s intimate enough that you won’t be too overwhelmed.&lt;/p&gt;&lt;p&gt;– Jillian Meehan&lt;/p&gt;&lt;p&gt;&lt;strong&gt;You might like Layers if:&lt;/strong&gt; you want to be around the WWDC hype, but you don’t actually want to go to WWDC.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f0e208c936c3a89cb0b80a3046336a5ff0e99dcd-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/f0e208c936c3a89cb0b80a3046336a5ff0e99dcd-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/f0e208c936c3a89cb0b80a3046336a5ff0e99dcd-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/f0e208c936c3a89cb0b80a3046336a5ff0e99dcd-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/f0e208c936c3a89cb0b80a3046336a5ff0e99dcd-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/f0e208c936c3a89cb0b80a3046336a5ff0e99dcd-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/f0e208c936c3a89cb0b80a3046336a5ff0e99dcd-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/f0e208c936c3a89cb0b80a3046336a5ff0e99dcd-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3024 3024w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f0e208c936c3a89cb0b80a3046336a5ff0e99dcd-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1600&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/7360a2b18ebe12fd60f1b50b2656fb56ac5e8aad-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/7360a2b18ebe12fd60f1b50b2656fb56ac5e8aad-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/7360a2b18ebe12fd60f1b50b2656fb56ac5e8aad-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/7360a2b18ebe12fd60f1b50b2656fb56ac5e8aad-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/7360a2b18ebe12fd60f1b50b2656fb56ac5e8aad-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/7360a2b18ebe12fd60f1b50b2656fb56ac5e8aad-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/7360a2b18ebe12fd60f1b50b2656fb56ac5e8aad-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/7360a2b18ebe12fd60f1b50b2656fb56ac5e8aad-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3024 3024w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/7360a2b18ebe12fd60f1b50b2656fb56ac5e8aad-3024x3024.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1600&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;The better WWDC&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;&lt;a href=&quot;https://2018.xoxofest.com&quot;&gt;XOXO&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;em&gt;Portland, Oregon&lt;/em&gt;&lt;/p&gt;&lt;p&gt;I just got back from my first XOXO last week, and all I can say is that it was magical. Aside from the great speakers, the arcade full of soon-to-be-released indie games, and the karaoke bar that was popping every night, the community of people from all corners of the internet that XOXO manages to bring together is fantastic. It’s hard to imagine a conference that’s more well-organized and cares about its attendees more than this one.&lt;/p&gt;&lt;p&gt;– Jillian Meehan&lt;/p&gt;&lt;p&gt;&lt;strong&gt;You might like XOXO if:&lt;/strong&gt; you want to meet your internet friends IRL and start up a new side-hustle with them.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9c7d9ae91d67b79ead277bd9402e5157d3f069da-1080x1080.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=270 270w, https://cdn.sanity.io/images/nkt6o869/production/9c7d9ae91d67b79ead277bd9402e5157d3f069da-1080x1080.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=540 540w, https://cdn.sanity.io/images/nkt6o869/production/9c7d9ae91d67b79ead277bd9402e5157d3f069da-1080x1080.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=810 810w, https://cdn.sanity.io/images/nkt6o869/production/9c7d9ae91d67b79ead277bd9402e5157d3f069da-1080x1080.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1080 1080w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9c7d9ae91d67b79ead277bd9402e5157d3f069da-1080x1080.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1080&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;The main stage at this year’s XOXO was 🔥&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;&lt;a href=&quot;https://2017.releasenotes.tv&quot;&gt;Release Notes&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;em&gt;Chicago, Illinois&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Release Notes is a conference about the business of making and releasing software. Coming out of a podcast of the same name, Joe and Charles book incredible speakers year after year (&lt;a href=&quot;https://youtu.be/W5IfEAncPPI&quot;&gt;oh, and me once, also&lt;/a&gt;). It’s great because we head people sharing real numbers, and stories about how they have or almost failed at making their business sustainable, and even sometimes what actually happened to work. Good pals, good talks, and a dine-around where you get to explore the city at a small dinner with other attendees, I couldn’t ask for a more thoughtfully curated conference.&lt;/p&gt;&lt;p&gt;– mb Bischoff&lt;/p&gt;&lt;p&gt;&lt;strong&gt;You might like Release Notes if:&lt;/strong&gt; you run a software company, are trying to figure out how much to charge for your app, or are scared of marketing.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f9a57d2b3f2881732c775f99c120e88d203ead0c-4000x2667.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/f9a57d2b3f2881732c775f99c120e88d203ead0c-4000x2667.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/f9a57d2b3f2881732c775f99c120e88d203ead0c-4000x2667.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/f9a57d2b3f2881732c775f99c120e88d203ead0c-4000x2667.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/f9a57d2b3f2881732c775f99c120e88d203ead0c-4000x2667.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/f9a57d2b3f2881732c775f99c120e88d203ead0c-4000x2667.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/f9a57d2b3f2881732c775f99c120e88d203ead0c-4000x2667.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/f9a57d2b3f2881732c775f99c120e88d203ead0c-4000x2667.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f9a57d2b3f2881732c775f99c120e88d203ead0c-4000x2667.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1067&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;tbt to mb speaking at Release Notes&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;&lt;a href=&quot;https://cocoaconf.com/&quot;&gt;CocoaConf Yosemite&lt;/a&gt; (r.i.p.)&lt;/h3&gt;&lt;p&gt;&lt;em&gt;Yosemite National Park&lt;/em&gt;&lt;/p&gt;&lt;p&gt;My favorite conference experience in my years in Apple development was Yosemite by CocoaConf. While the speaker lineup was of incredibly high quality, what set this conference apart from the rest was its location in Yosemite National Park, arguably one of the most beautiful places in the country. Between sessions, there were guided hikes and photo walks around breathtaking mountains and waterfalls. The setting made it easy to leave the stresses and anxieties back home and give full attention to the wonderful talks from the incredibly talented speakers.&lt;/p&gt;&lt;p&gt;– Michael Liberatore&lt;/p&gt;&lt;p&gt;&lt;strong&gt;You might like Yosemite if:&lt;/strong&gt; you enjoy technical talks and networking while fully disconnecting from the worries back home (or you just want an excuse for work to pay for your hiking trip).&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c264d23e87050af720dbb12a8b2b415ba5f1e16c-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/c264d23e87050af720dbb12a8b2b415ba5f1e16c-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/c264d23e87050af720dbb12a8b2b415ba5f1e16c-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/c264d23e87050af720dbb12a8b2b415ba5f1e16c-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/c264d23e87050af720dbb12a8b2b415ba5f1e16c-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/c264d23e87050af720dbb12a8b2b415ba5f1e16c-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/c264d23e87050af720dbb12a8b2b415ba5f1e16c-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/c264d23e87050af720dbb12a8b2b415ba5f1e16c-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c264d23e87050af720dbb12a8b2b415ba5f1e16c-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;The actual conference was cool, too&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;&lt;a href=&quot;https://2016.ull.ie&quot;&gt;Úll&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;em&gt;Killarney, Ireland&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Úll is magical. It takes place in Ireland, and it’s expensive, but everything is taken care of. When I’ve attended, it’s boasted an amazingly diverse set of speakers, experiences, and a much less US-centric crowd. You ride a train across the Irish countryside with your fellow attendees, stay in a hotel with gorgeous views, and learn from folks in all sorts of industries about how they put care and detail into their work. There have been talks, storytelling, science fairs, escape rooms, and so much more. It’s a venue where I was comfortable giving my most vulnerable talk ever, a storytelling slot I used to publicly process the death of Quotebook. If there’s another Úll and you have the money, just go. You’ll see.&lt;/p&gt;&lt;p&gt;– mb Bischoff&lt;/p&gt;&lt;p&gt;&lt;strong&gt;You might like Úll if:&lt;/strong&gt; you’ve never been to Ireland, you “trust the process,” or you feel creatively stuck on what to do next or why your work matters.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/fe0609152b4ed621b3489efa67a7a19d20067675-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/fe0609152b4ed621b3489efa67a7a19d20067675-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/fe0609152b4ed621b3489efa67a7a19d20067675-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/fe0609152b4ed621b3489efa67a7a19d20067675-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/fe0609152b4ed621b3489efa67a7a19d20067675-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/fe0609152b4ed621b3489efa67a7a19d20067675-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/fe0609152b4ed621b3489efa67a7a19d20067675-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/fe0609152b4ed621b3489efa67a7a19d20067675-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/fe0609152b4ed621b3489efa67a7a19d20067675-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;How is this a real place?&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;What else?&lt;/h3&gt;&lt;p&gt;We’re always looking to expand our horizons, conference-wise, and there are tons of great events that we’d love to check out but haven’t gotten a chance to. If there are any conferences you love and want to spread the word about, let us know!&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>A Tale of Two App Store Spotlights</title><link>https://lickability.com/blog/a-tale-of-two-app-store-spotlights/</link><guid isPermaLink="true">https://lickability.com/blog/a-tale-of-two-app-store-spotlights/</guid><description>Getting featured increased our downloads by over 9,000% 🤯</description><pubDate>Thu, 06 Sep 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Some good news for Lickability’s products! A few weeks ago, both of our iOS apps (&lt;a href=&quot;https://itunes.apple.com/us/app/pinpoint-screenshot-editor/id669858907?mt=8&quot;&gt;Pinpoint&lt;/a&gt; and &lt;a href=&quot;https://itunes.apple.com/us/app/id675410630?mt=8&amp;ign-mpt=uo%3D4&quot;&gt;Accelerator&lt;/a&gt;) were simultaneously featured on the App Store after recent updates we’ve made.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b6224392267d7c68d78227f350f339434d061f3a-4000x4000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/b6224392267d7c68d78227f350f339434d061f3a-4000x4000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/b6224392267d7c68d78227f350f339434d061f3a-4000x4000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/b6224392267d7c68d78227f350f339434d061f3a-4000x4000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/b6224392267d7c68d78227f350f339434d061f3a-4000x4000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/b6224392267d7c68d78227f350f339434d061f3a-4000x4000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/b6224392267d7c68d78227f350f339434d061f3a-4000x4000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/b6224392267d7c68d78227f350f339434d061f3a-4000x4000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b6224392267d7c68d78227f350f339434d061f3a-4000x4000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1600&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;We saw significant spikes in downloads and sales, and because they were featured at the same time, we saw a small but noticeable halo effect from customers who mentioned that they picked up both apps at once.&lt;/p&gt;&lt;h3&gt;🎉 The Features&lt;/h3&gt;&lt;p&gt;The App Store editorial team featured Pinpoint in a roundup of &lt;a href=&quot;https://itunes.apple.com/us/story/id1427696209&quot;&gt;&lt;em&gt;5 Apps We Love Right Now&lt;/em&gt;&lt;/a&gt; on the Today tab, and Accelerator under a similar heading on the Apps tab. It seems like the folks over in editorial have a lot of ❤️ for Lickability these days.&lt;/p&gt;&lt;h3&gt;📈 The Results&lt;/h3&gt;&lt;p&gt;So, what’d these features do for our apps? Let’s hop on over to &lt;a href=&quot;https://appfigures.com&quot;&gt;AppFigures&lt;/a&gt;, the service we use to monitor sales and downloads, and look at some graphs.&lt;/p&gt;&lt;p&gt;Here’s what the features did for Accelerator’s sales:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/294bb9caece69d4fadc637be3ec3f51de1377b48-848x382.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=212 212w, https://cdn.sanity.io/images/nkt6o869/production/294bb9caece69d4fadc637be3ec3f51de1377b48-848x382.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=424 424w, https://cdn.sanity.io/images/nkt6o869/production/294bb9caece69d4fadc637be3ec3f51de1377b48-848x382.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=636 636w, https://cdn.sanity.io/images/nkt6o869/production/294bb9caece69d4fadc637be3ec3f51de1377b48-848x382.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=848 848w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/294bb9caece69d4fadc637be3ec3f51de1377b48-848x382.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=848&quot; width=&quot;848&quot; height=&quot;382&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Accelerator sales graph Aug 9–Aug 22&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;And for Pinpoint’s downloads:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/246ff711a450cedc2fc83691bc969113001faa5b-840x380.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=210 210w, https://cdn.sanity.io/images/nkt6o869/production/246ff711a450cedc2fc83691bc969113001faa5b-840x380.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=420 420w, https://cdn.sanity.io/images/nkt6o869/production/246ff711a450cedc2fc83691bc969113001faa5b-840x380.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=630 630w, https://cdn.sanity.io/images/nkt6o869/production/246ff711a450cedc2fc83691bc969113001faa5b-840x380.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=840 840w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/246ff711a450cedc2fc83691bc969113001faa5b-840x380.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=840&quot; width=&quot;840&quot; height=&quot;380&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Pinpoint downloads graph Aug 9–Aug 22&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;As the graphs show, for both free and paid apps, getting featured on the App Store can have a pretty massive impact. In our case, we saw a &lt;strong&gt;9,000% increase in average daily downloads of our free (with IAP) app and a 900% increase in downloads per day for our paid up-front app.&lt;/strong&gt;&lt;/p&gt;&lt;h3&gt;⏳ More to Come?&lt;/h3&gt;&lt;p&gt;Apple also requested artwork to feature both applications a few weeks ago, but hasn’t used it just yet. Here’s a sneak peek of some art you &lt;em&gt;might&lt;/em&gt; see soon on an App Store near you…&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/8594f9c1bd9c81a5e75e9af0648353f72f99b38f-2000x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/8594f9c1bd9c81a5e75e9af0648353f72f99b38f-2000x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/8594f9c1bd9c81a5e75e9af0648353f72f99b38f-2000x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/8594f9c1bd9c81a5e75e9af0648353f72f99b38f-2000x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/8594f9c1bd9c81a5e75e9af0648353f72f99b38f-2000x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/8594f9c1bd9c81a5e75e9af0648353f72f99b38f-2000x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c84065c82775651135f2addc2a590292ff28be10-4000x1000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/c84065c82775651135f2addc2a590292ff28be10-4000x1000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/c84065c82775651135f2addc2a590292ff28be10-4000x1000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/c84065c82775651135f2addc2a590292ff28be10-4000x1000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/c84065c82775651135f2addc2a590292ff28be10-4000x1000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/c84065c82775651135f2addc2a590292ff28be10-4000x1000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/c84065c82775651135f2addc2a590292ff28be10-4000x1000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/c84065c82775651135f2addc2a590292ff28be10-4000x1000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c84065c82775651135f2addc2a590292ff28be10-4000x1000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;400&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;If you’re looking to get your app featured by Apple, start by making something great (&lt;a href=&quot;https://lickability.com/about&quot;&gt;we can help with that part&lt;/a&gt; 😉) and getting it in the hands of customers. Once you’ve worked out the kinks and are ready for some more attention, check out &lt;a href=&quot;https://developer.apple.com/app-store/marketing/guidelines/&quot;&gt;Apple’s Marketing Resources&lt;/a&gt; and the &lt;a href=&quot;https://releasenotes.tv&quot;&gt;Release Notes podcast&lt;/a&gt; for tips on getting the attention of App Store editors. We hope to share the limelight with you soon!&lt;/p&gt; </content:encoded><author>mb bischoff</author></item><item><title>Slack is for Friends, Too</title><link>https://lickability.com/blog/slack-is-for-friends-too/</link><guid isPermaLink="true">https://lickability.com/blog/slack-is-for-friends-too/</guid><description>How we use Slack to build the Lickability community</description><pubDate>Thu, 26 Jul 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Because we’re a small company, we like to make an effort to welcome people outside of our 7-person office into the Lickability community. The best way to do that, we’ve found, is to invite friends (and friends of friends) of the company to join the Lickability Slack.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b02a9eca4ab492f14729fc55b4011c124ab21bcf-510x440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=255 255w, https://cdn.sanity.io/images/nkt6o869/production/b02a9eca4ab492f14729fc55b4011c124ab21bcf-510x440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=510 510w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b02a9eca4ab492f14729fc55b4011c124ab21bcf-510x440.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=510&quot; width=&quot;510&quot; height=&quot;440&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;How?&lt;/h3&gt;&lt;p&gt;The full-time team at Lickability has 7 people, but the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;#general&lt;/code&gt; channel in our Slack has almost 30 members. That’s because we’ve filled it up with smart, interesting people who we find valuable to have in our virtual office. &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;#general&lt;/code&gt; (and other public channels like &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;#food&lt;/code&gt; and &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;#music&lt;/code&gt; and &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;#games&lt;/code&gt;) is our water cooler; it’s a place where we can take a quick break from work and catch up with our friends. Or, in our &lt;code index=&quot;11&quot; isInline=&quot;true&quot;&gt;#ask-anything&lt;/code&gt; channel, we can ask and answer questions anyone in the Slack might have — about engineering, designing, running a business, etc. We’ve also invited beta testers into our Slack as single-channel guests to give them a place to discuss the app they’re testing and give us direct feedback.&lt;/p&gt;&lt;p&gt;Our community extends outside Slack, too. We were lucky enough to meet up with some of our Slack members &lt;a href=&quot;https://blog.lickability.com/wwdc-and-layers-2018-eae74f04f5e6&quot;&gt;in San Jose&lt;/a&gt; at WWDC, and a few of us who are based in New York got together last week for drinks at one of our favorite &lt;a href=&quot;http://www.pouringribbons.com&quot;&gt;cocktail spots&lt;/a&gt; in the city.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/1dc6bf9b994defe8c70a690a4f47bec6d4da8d32-992x1022.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=248 248w, https://cdn.sanity.io/images/nkt6o869/production/1dc6bf9b994defe8c70a690a4f47bec6d4da8d32-992x1022.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=496 496w, https://cdn.sanity.io/images/nkt6o869/production/1dc6bf9b994defe8c70a690a4f47bec6d4da8d32-992x1022.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=744 744w, https://cdn.sanity.io/images/nkt6o869/production/1dc6bf9b994defe8c70a690a4f47bec6d4da8d32-992x1022.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=992 992w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/1dc6bf9b994defe8c70a690a4f47bec6d4da8d32-992x1022.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=992&quot; width=&quot;992&quot; height=&quot;1022&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Why?&lt;/h3&gt;&lt;p&gt;The bottom line is we love our pals and we’re lucky to have such a great group of people in our community. Opening up our Slack to friends of the company has undeniably made our work life better. We’re not a large company, so it’s nice to bring in more folks to foster discussion, get different opinions on things, and just generally have a good time.&lt;/p&gt;&lt;h3&gt;Should I do it too?&lt;/h3&gt;&lt;p&gt;It depends. We’ve found a lot of value in having friends in our Slack, but there can be downsides. We have to keep most work discussions locked down in private channels, and (as anything) it can be a bit distracting to have non-work discussions on the clock. This might not be the best Slack strategy for larger companies, but we ultimately recommend it for anyone looking to foster a better community at work. &lt;em&gt;Here’s how to do it:&lt;/em&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;If you’re not sure who to invite to your own office Slack, look for people who share the same vision and goals as you, people you like working &lt;em&gt;and&lt;/em&gt; getting lunch with, and a healthy mix of people both in your city and in others—the point is to get out of your small company bubble and to bring fun discussion and fresh perspectives into the mix.&lt;/li&gt;&lt;li&gt;There’s no need to keep everybody confined to &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;#general&lt;/code&gt;. If you don’t already have public channels for people to talk about things happening outside of your office, make some! A few of our favorites are &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;#food&lt;/code&gt;, &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;#games&lt;/code&gt;, &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;#music&lt;/code&gt;, and &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;#politics&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;We recommend making a &lt;a href=&quot;https://github.com/Lickability/code-of-conduct&quot;&gt;Code of Conduct&lt;/a&gt; if you don’t have one already. It’s good to lay down a few ground rules for what’s okay and what’s not if you’re inviting people outside of the company in.&lt;/li&gt;&lt;li&gt;Don’t forget to hang out with your Slack friends IRL sometimes! It’s cool to chat online, but it’s even cooler to make an effort to see people face-to-face when you have the opportunity.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;We certainly don’t have all the answers, but this is what works for us. If you decide to try it out, let us know! Or, if you’re a friend of the company and want to join &lt;em&gt;our&lt;/em&gt; Slack, get in touch with us on Twitter at &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;@lickability&lt;/a&gt;. We’d love to have you.&lt;/p&gt; </content:encoded><author>Jillian Meehan</author></item><item><title>🐞 Insidious Bugs #2</title><link>https://lickability.com/blog/insidious-bugs-2-wait-for-executable-to-be-launched/</link><guid isPermaLink="true">https://lickability.com/blog/insidious-bugs-2-wait-for-executable-to-be-launched/</guid><description>Wait for Executable to be Launched</description><pubDate>Thu, 12 Jul 2018 00:00:00 GMT</pubDate><content:encoded>&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;&lt;em&gt;Insidious Bugs&lt;/em&gt; is an occasional series in which we share stories of overcoming obscure issues in iOS development with the goal of saving you the time we lost, should you encounter the issues yourself.&lt;/p&gt;  &lt;/aside&gt;&lt;h3&gt;The Bug&lt;/h3&gt;&lt;p&gt;When setting up a scheme to wait until the app is launched to attach the debugger, performing a “Build &amp;amp; Run” will build the app and prepare it to be launched, but will not deploy a new app bundle. Because of that, you won’t see bug fixes or the effects of other code changes you may have just made.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/4c57f1648512c24bc2280acba75a3afc02d0f36e-1912x1128.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/4c57f1648512c24bc2280acba75a3afc02d0f36e-1912x1128.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/4c57f1648512c24bc2280acba75a3afc02d0f36e-1912x1128.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/4c57f1648512c24bc2280acba75a3afc02d0f36e-1912x1128.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/4c57f1648512c24bc2280acba75a3afc02d0f36e-1912x1128.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1912 1912w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/4c57f1648512c24bc2280acba75a3afc02d0f36e-1912x1128.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;944&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;The innocuous-looking setting&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;The Solution&lt;/h3&gt;&lt;p&gt;To make sure that any changes we make are deployed, we first need to set up the scheme to automatically launch the app, perform a “Build &amp;amp; Run” to deploy our changes, and then switch the scheme back to waiting for the app to launch to attach the debugger.&lt;/p&gt;&lt;h3&gt;How We Got There&lt;/h3&gt;&lt;p&gt;We came across this issue while expanding on the Today Extension built out in our &lt;a href=&quot;https://blog.lickability.com/insidious-bugs-1-today-extensions-345fb3bcd6a2&quot;&gt;first Insidious Bugs post&lt;/a&gt;. To recap, we had built a Today Extension that is given a set of tweets from its host application, and then displays those tweets in a table view. This has been working well, but we want to give the user a way to see more details about the tweet in the host application. Thus, we went about adding functionality to open a tweet when you tap on it in the Today Extension.&lt;/p&gt;&lt;p&gt;To achieve this, we came up with a URL scheme for our host application to support, along with a URL structure to allow deep linking to a specific tweet. Upon receiving the deep link, the host application would create a view controller to show the tweet and push it onto the navigation stack.&lt;/p&gt;&lt;p&gt;To test this functionality, we switched the “Launch” setting in the scheme to “Wait for executable to be launched,” built and ran the app, opened the widget, and then tapped on a tweet. The app launched, but the details on the tweet we tapped in the widget didn’t show up.&lt;/p&gt;&lt;p&gt;This was certainly not what we expected so we set a breakpoint at the start of our &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623112-application&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;application(\_:open:options:)&lt;/code&gt;&lt;/a&gt; delegate method, and re-ran the app. Sure enough, our delegate method wasn’t being called. Wait, what?&lt;/p&gt;&lt;p&gt;This definitely didn’t seem right. The first thing to do was double check documentation and confirm that we implemented deep linking correctly. The docs confirmed that we had implemented the correct delegate method, and added the appropriate information to our Info.plist.&lt;/p&gt;&lt;p&gt;After consulting documentation, we tried reverting how the app launched when we did a build and run, switching back to having the app launch automatically. The app launched, we minimized it, went to our widget, and tapped a tweet.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/0ff3dcdabedc2afae8064c0546244b9a7086ac63-1196x725.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=299 299w, https://cdn.sanity.io/images/nkt6o869/production/0ff3dcdabedc2afae8064c0546244b9a7086ac63-1196x725.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=598 598w, https://cdn.sanity.io/images/nkt6o869/production/0ff3dcdabedc2afae8064c0546244b9a7086ac63-1196x725.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=897 897w, https://cdn.sanity.io/images/nkt6o869/production/0ff3dcdabedc2afae8064c0546244b9a7086ac63-1196x725.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1196 1196w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/0ff3dcdabedc2afae8064c0546244b9a7086ac63-1196x725.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1196&quot; width=&quot;1196&quot; height=&quot;725&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;It… worked?&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Huzzah! Deep linking was successfully implemented. However, we were still unsure of why it didn’t work when we waited to attach the debugger until the app was launched. This required further investigation.&lt;/p&gt;&lt;p&gt;The first step was to switch back the launch setting. Then, we changed the background color of the tweet detail view controller (a low effort change to make, but one with a big impact that was easy to verify). We built and ran the app and tapped on a tweet in the widget. The app deep linked into the detail view controller and, as we expected, it did not have the new background color.&lt;/p&gt;&lt;p&gt;This seemed to confirm suspicions that if we had the “Wait for executable to be launched” option selected, while Xcode would build the app, it would not actually deploy the newly built app. To confirm our hunch, there was one last thing we could do: we could delete the app from the simulator. If our suspicions were true, then doing a build and run with “Wait for executable to be launched” should not install the app, which means we shouldn’t see its icon anywhere on the home screen.&lt;/p&gt;&lt;p&gt;We deleted the app from the simulator, built and ran, and took a look at the home screen. Lo and behold, the app was nowhere to be found. With this final bit of confirmation, we felt confident that our hypothesis was true.&lt;/p&gt;&lt;h3&gt;Radar&lt;/h3&gt;&lt;p&gt;We’ve filed a bug report with Apple to get this issue fixed:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://openradar.appspot.com/radar?id=4983181564444672&quot;&gt;Build &amp;amp; Run While Waiting to Attach the Debugger Does Not Redeploy an Application (41436011, closed as a duplicate of 22866783, which is still open)&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; </content:encoded><author>Grant Butler</author></item><item><title>Our Week in San Jose</title><link>https://lickability.com/blog/our-week-in-san-jose/</link><guid isPermaLink="true">https://lickability.com/blog/our-week-in-san-jose/</guid><description>It’s the WWDC post you’ve been waiting for</description><pubDate>Thu, 14 Jun 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you spent any time on the internet last week, you &lt;em&gt;may&lt;/em&gt; have noticed that a few of us were in San Jose for a little event known as WWDC. Now we’re back, and we can’t wait to tell you all about it!&lt;/p&gt;&lt;h3&gt;#WWDC18 🎉&lt;/h3&gt;&lt;p&gt;mb, Brian, and Jillian (hi 👋) all flew out to California for a smattering of conferences, live podcast recordings, and &lt;a href=&quot;https://twitter.com/lickability/status/999405811322490881&quot;&gt;pin exchanges&lt;/a&gt;. mb was Lickability’s person in the field at WWDC all week, while Brian and Jillian attended &lt;a href=&quot;https://layers.is&quot;&gt;Layers&lt;/a&gt; for three days of snacks, fun design talks, and more snacks.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Photo of the WWDC keynote showing a wall of app icons.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/8d0ac829df46398b489746e6438a93abb0a01d70-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/8d0ac829df46398b489746e6438a93abb0a01d70-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/8d0ac829df46398b489746e6438a93abb0a01d70-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/8d0ac829df46398b489746e6438a93abb0a01d70-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/8d0ac829df46398b489746e6438a93abb0a01d70-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/8d0ac829df46398b489746e6438a93abb0a01d70-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2048 2048w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/8d0ac829df46398b489746e6438a93abb0a01d70-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;You know what it is.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Everyone back at Lickability HQ enjoyed WWDC too. The day of the keynote, we had &lt;a href=&quot;https://twitter.com/lickability/status/1003678478179299328&quot;&gt;mb&lt;/a&gt; watching at the conference center, &lt;a href=&quot;https://twitter.com/lickability/status/1003683626620293120&quot;&gt;Brian and Jillian&lt;/a&gt; watching via iPad in a hotel room, and &lt;a href=&quot;https://twitter.com/grantjbutler/status/1003681036952317952&quot;&gt;the rest of the team&lt;/a&gt; watching on the big screen over pizza back in New York.We’re all still catching up on WWDC videos, but here are some of our favorites from last week:&lt;/p&gt;&lt;h4&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/401&quot;&gt;What’s New in Swift&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Michael Liberatore:&lt;/p&gt;&lt;blockquote&gt;I love seeing Apple highlight the open source efforts of the community, and Swift’s constant quality-of-life improvements have me excited for the future. I ❤️ &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Case​Iterable&lt;/code&gt;, and that &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Equatable&lt;/code&gt; and &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Hashable&lt;/code&gt; can now be synthesized!&lt;/blockquote&gt;&lt;h4&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/803&quot;&gt;Designing Fluid Interfaces&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;mb Bischoff:&lt;/p&gt;&lt;blockquote&gt;I liked Designing Fluid Interfaces because they showed a ton of custom rendered videos that explained how and why they made the iPhone X feel so fluid to use.&lt;/blockquote&gt;&lt;h4&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/402&quot;&gt;Getting the Most out of Playgrounds in Xcode&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Michael Amundsen:&lt;/p&gt;&lt;blockquote&gt;I don’t use Playgrounds much, but I think they did a good job at explaining some real world use cases of why you should use them even as part of a normal project.&lt;/blockquote&gt;&lt;h4&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/804/&quot;&gt;The Life of a Button&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;mb Bischoff:&lt;/p&gt;&lt;blockquote&gt;It takes something so small (a single button) and walks through the entire design process of how it should work, behave, be titled, etc.&lt;/blockquote&gt;&lt;h4&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/233&quot;&gt;Adding Delight to Your iOS App&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;mb Bischoff:&lt;/p&gt;&lt;blockquote&gt;It made me think a lot about “Layout Driven UI” which is not something I’d seen written about or described much before.&lt;/blockquote&gt;&lt;p&gt;Michael Liberatore:&lt;/p&gt;&lt;blockquote&gt;I really like these kinds of sessions that are not introducing anything new, API-wise, but provide alternative ways to think about writing code with the same frameworks and tools.&lt;/blockquote&gt;&lt;h3&gt;We also ❤️ Layers&lt;/h3&gt;&lt;p&gt;None of the talks from Layers are available to watch online (yet), but we want to give a special shout-out to a few of our favorites: &lt;a href=&quot;https://twitter.com/jtaby?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor&quot;&gt;Majd Taby&lt;/a&gt; gave a very moving talk about his journey toward co-founding &lt;a href=&quot;https://itunes.apple.com/us/app/darkroom-photo-editor/id953286746?mt=8&quot;&gt;Darkroom&lt;/a&gt; and creating &lt;a href=&quot;https://syriandiaspora.com&quot;&gt;Displaced&lt;/a&gt;; &lt;a href=&quot;https://twitter.com/lithajozi?lang=en&quot;&gt;Litha Soyizwapi&lt;/a&gt;’s story about developing and designing the &lt;a href=&quot;https://itunes.apple.com/us/app/gaurider/id628358170?mt=8&quot;&gt;GauRider&lt;/a&gt; app was inspiring; &lt;a href=&quot;https://twitter.com/warpling&quot;&gt;Ryan McLeod&lt;/a&gt; had a magical presentation about sleight of hand in digital design; and &lt;a href=&quot;https://twitter.com/jessicahische?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor&quot;&gt;Jessica Hische&lt;/a&gt; is a fantastic speaker and one of our favorite people. If you get a chance to go to Layers next year, &lt;em&gt;do it&lt;/em&gt;.&lt;/p&gt;&lt;blockquote&gt;Here we go! 😅🤞🏻&lt;a href=&quot;https://twitter.com/hashtag/layersconf?src=hash&amp;ref_src=twsrc%5Etfw&quot;&gt;#layersconf&lt;/a&gt;&lt;a href=&quot;https://t.co/0NRB0KESl7&quot;&gt;pic.twitter.com/0NRB0KESl7&lt;/a&gt;&lt;br&gt;&lt;br&gt;— Ryan McLeod (@warpling) &lt;a href=&quot;https://twitter.com/warpling/status/1004438043791519744?ref_src=twsrc%5Etfw&quot;&gt;June 6, 2018&lt;/a&gt;&lt;/blockquote&gt;&lt;h3&gt;See you next year! 👋&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;A Lickability dinner at The Farmers Union with friends.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/fe7d6e28308da62a756f22a8fdae65724aaf476a-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/fe7d6e28308da62a756f22a8fdae65724aaf476a-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/fe7d6e28308da62a756f22a8fdae65724aaf476a-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/fe7d6e28308da62a756f22a8fdae65724aaf476a-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/fe7d6e28308da62a756f22a8fdae65724aaf476a-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/fe7d6e28308da62a756f22a8fdae65724aaf476a-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2048 2048w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/fe7d6e28308da62a756f22a8fdae65724aaf476a-2048x1536.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;A very lickable dinner at The Farmers Union 👅&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;We were lucky enough to catch up with a bunch of our friends while we were in San Jose, and even luckier to meet so many people we didn’t know before. One of our goals for the week was to hand out Lickability pins and stickers to as many people as possible, and we definitely pulled it off — we don’t have a single Lickability pin left. If you got one, give us a shout-out on &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;Twitter&lt;/a&gt; or &lt;a href=&quot;https://www.instagram.com/lickability/&quot;&gt;Instagram&lt;/a&gt; so we can see it in its new home! And if you didn’t get one, let us know — we’ll make sure you get one next time.&lt;/p&gt; </content:encoded><author>Team Lickability</author></item><item><title>Inside Lickability HQ</title><link>https://lickability.com/blog/inside-lickability-hq/</link><guid isPermaLink="true">https://lickability.com/blog/inside-lickability-hq/</guid><description>Making our office as lickable as our apps</description><pubDate>Wed, 16 May 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We’ve been in our current office for almost a year without adding our own Lickability flair to it. Save for the logo on the door and the people inside, it could have been anyone’s office. But in the past few months, we’ve started redecorating the space to make it more our own.&lt;/p&gt;&lt;p&gt;It’s important to us to feel comfortable and motivated at work—that’s why we give every new employee the opportunity to build a workstation setup they love. We thought it was about time to do the same with the rest of our office.&lt;/p&gt;&lt;blockquote class=&quot;quote-block&quot;&gt; &lt;p class=&quot;quote-text&quot;&gt;Before this project, our office felt like a generic glass cube. Now it feels like a space where a bunch of creative nerds can come together to make jokes, make apps, and have a cider after work once in a while.&lt;/p&gt; &lt;cite class=&quot;quote-author&quot;&gt;mb Bischoff&lt;/cite&gt; &lt;/blockquote&gt;&lt;h3&gt;Step 1&lt;/h3&gt;&lt;p&gt;The first thing we needed was art. We could have gotten any generic art pieces to liven the space up, but we wanted something that felt like us. So we started with fracture art of icons for apps we’ve worked on — some of our own, like &lt;a href=&quot;https://blog.lickability.com/the-end-of-quotebook-9e19b5653cc9&quot;&gt;Quotebook&lt;/a&gt; (r.i.p.) and &lt;a href=&quot;https://blog.lickability.com/we-re-super-excited-to-announce-our-newest-app-pinpoint-is-available-now-for-free-on-the-app-a9bf11bff0b1&quot;&gt;Pinpoint&lt;/a&gt;; and some of our clients, like &lt;a href=&quot;https://blog.lickability.com/aloe-bud-self-care-app-5408bb9827f8&quot;&gt;Aloe Bud&lt;/a&gt; and &lt;a href=&quot;https://blog.lickability.com/a-new-look-for-the-atlantic-app-a6bae3931a5b&quot;&gt;The Atlantic&lt;/a&gt;. It’s nice to have a reminder of the work we’ve done hanging on the walls like a motivational poster. Plus, they add lots of great color to the space.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;Two desks at the Lickability office with some app icon artwork hung above them.&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c854ee429d39e2a19350655f851f42cb0b8bab41-4000x3000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/c854ee429d39e2a19350655f851f42cb0b8bab41-4000x3000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/c854ee429d39e2a19350655f851f42cb0b8bab41-4000x3000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/c854ee429d39e2a19350655f851f42cb0b8bab41-4000x3000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/c854ee429d39e2a19350655f851f42cb0b8bab41-4000x3000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/c854ee429d39e2a19350655f851f42cb0b8bab41-4000x3000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/c854ee429d39e2a19350655f851f42cb0b8bab41-4000x3000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/c854ee429d39e2a19350655f851f42cb0b8bab41-4000x3000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c854ee429d39e2a19350655f851f42cb0b8bab41-4000x3000.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Some of our fractures for Meetup, Quotebook, and New Yorker Today.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;Step 2&lt;/h3&gt;&lt;p&gt;Next: plants, a staple of any office. But before you can have plants, you need something to put them in. So we reached out to Brooklyn-based ceramicist &lt;a href=&quot;https://marianbull.bigcartel.com&quot;&gt;Marian Bull&lt;/a&gt; for custom, Lickability Red planters, and we couldn’t be happier with the result. Marian’s work added much-needed personality to the office. From there, we filled our new planters with (easy to care for, we hope) plants from &lt;a href=&quot;https://www.thesill.com&quot;&gt;The Sill&lt;/a&gt;. We’re going to try to keep these ones alive for a while.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6163596cea9dba93fc0ce6fa39c74387ddb624ea-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/6163596cea9dba93fc0ce6fa39c74387ddb624ea-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/6163596cea9dba93fc0ce6fa39c74387ddb624ea-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/6163596cea9dba93fc0ce6fa39c74387ddb624ea-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/6163596cea9dba93fc0ce6fa39c74387ddb624ea-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/6163596cea9dba93fc0ce6fa39c74387ddb624ea-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/6163596cea9dba93fc0ce6fa39c74387ddb624ea-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/6163596cea9dba93fc0ce6fa39c74387ddb624ea-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3024 3024w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6163596cea9dba93fc0ce6fa39c74387ddb624ea-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;2133&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d9e967c8dd396fbb78983db0569b52f0181755dd-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/d9e967c8dd396fbb78983db0569b52f0181755dd-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/d9e967c8dd396fbb78983db0569b52f0181755dd-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/d9e967c8dd396fbb78983db0569b52f0181755dd-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/d9e967c8dd396fbb78983db0569b52f0181755dd-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/d9e967c8dd396fbb78983db0569b52f0181755dd-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/d9e967c8dd396fbb78983db0569b52f0181755dd-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/d9e967c8dd396fbb78983db0569b52f0181755dd-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3024 3024w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d9e967c8dd396fbb78983db0569b52f0181755dd-3024x4032.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;2133&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;We ❤️ our new pals&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;Step 3&lt;/h3&gt;&lt;p&gt;And for the bonus round: we got Lickability-branded enamel pins and hoodies for the whole team! Thanks to &lt;a href=&quot;https://www.pingamestrong.com&quot;&gt;Pin Game Strong&lt;/a&gt; and &lt;a href=&quot;https://corp-couture.com&quot;&gt;Corporate Couture&lt;/a&gt;, we look just as great as our office does.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b82f990f12d9e28aff95a4c177811a422d9fdf44-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/b82f990f12d9e28aff95a4c177811a422d9fdf44-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/b82f990f12d9e28aff95a4c177811a422d9fdf44-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/b82f990f12d9e28aff95a4c177811a422d9fdf44-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/b82f990f12d9e28aff95a4c177811a422d9fdf44-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/b82f990f12d9e28aff95a4c177811a422d9fdf44-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/b82f990f12d9e28aff95a4c177811a422d9fdf44-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/b82f990f12d9e28aff95a4c177811a422d9fdf44-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b82f990f12d9e28aff95a4c177811a422d9fdf44-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt;  &lt;/figure&gt; &lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/7e897c930466beaa909102dd4c8c8e870965487d-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/7e897c930466beaa909102dd4c8c8e870965487d-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/7e897c930466beaa909102dd4c8c8e870965487d-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/7e897c930466beaa909102dd4c8c8e870965487d-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/7e897c930466beaa909102dd4c8c8e870965487d-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/7e897c930466beaa909102dd4c8c8e870965487d-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/7e897c930466beaa909102dd4c8c8e870965487d-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/7e897c930466beaa909102dd4c8c8e870965487d-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/7e897c930466beaa909102dd4c8c8e870965487d-4000x3000.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Our new pins and hoodies look so good you’ll want to lick them (but maybe don’t).&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;Profit&lt;/h3&gt;&lt;p&gt;In the last few months, we’ve succeeded in adding all of the above to some of the small, already-existing details that make our office fun: our standard &lt;a href=&quot;https://twitter.com/grantjbutler/status/927559505910665217&quot;&gt;new employee welcome kit&lt;/a&gt; of chocolate, pens, notebooks, and baby succulents; a reliable supply of Sour Patch Watermelon, our favorite 3 p.m. office snack; and our frequent use of &lt;a href=&quot;https://slack.com/apps/A0H2J7CMB-coffee-runner&quot;&gt;Coffee Runner&lt;/a&gt;, a Slack app that we tend to use more for snack runs than for Blue Bottle trips (though we use it plenty for the latter as well).&lt;/p&gt;&lt;p&gt;Our goal wasn’t just to decorate our office, but to decorate our office in a way only we could. We wanted to add life and personality to a space that was essentially just a tiny room full of desks and computers — and we did. We’ve always loved where we work, but now we love it just a little bit more.&lt;/p&gt;&lt;blockquote class=&quot;quote-block&quot;&gt; &lt;p class=&quot;quote-text&quot;&gt;Though some of these tweaks are small, they’ve really helped to add personality to our space. Our fractures hang like trophies from the projects we’ve been proud to be a part of, and it’s an exciting motivator to continue filling the walls with even more.&lt;/p&gt; &lt;cite class=&quot;quote-author&quot;&gt;Michael Liberatore&lt;/cite&gt; &lt;/blockquote&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;If you’re ever in New York and you’d like to come see what we’ve done with the place, &lt;strong&gt;let us know&lt;/strong&gt;—we’d love to have you come by. And if you’d like to work with us in our newly decorated office, you’re in luck: &lt;a href=&quot;https://lickability.com/jobs&quot;&gt;&lt;strong&gt;we’re hiring!&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Jillian Meehan</author></item><item><title>Aloe Bud is Here 🌱</title><link>https://lickability.com/blog/aloe-bud-is-here/</link><guid isPermaLink="true">https://lickability.com/blog/aloe-bud-is-here/</guid><description>You gotta nourish to flourish!</description><pubDate>Tue, 01 May 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Self care is important to us at Lickability. But, like everyone else, we sometimes struggle with remembering to take care of ourselves—sitting at a desk for hours at a time, working through lunch, and forgetting to drink water all day is not a good way to live.&lt;/p&gt;&lt;p&gt;That’s why we’re thrilled about the launch of &lt;a href=&quot;https://aloe.club&quot;&gt;Aloe Bud&lt;/a&gt;, the self-care pocket companion we’ve been working on with &lt;a href=&quot;https://x.com/serenitydiscko&quot;&gt;Serenity Discko&lt;/a&gt;. After a &lt;a href=&quot;https://www.kickstarter.com/projects/aloe/aloe-app-gentle-self-care-reminders-from-yourself&quot;&gt;successful Kickstarter campaign&lt;/a&gt; last summer, nine months of designing and developing the app, and several weeks of beta testing, Aloe Bud is finally &lt;a href=&quot;https://itunes.apple.com/app/apple-store/id1318382054?mt=8&quot;&gt;available for free on the App Store&lt;/a&gt; for all your self-care needs.&lt;/p&gt;&lt;h3&gt;What it is&lt;/h3&gt;&lt;p&gt;The idea for an app that sends gentle reminders to eat, drink water, and take breaks throughout the day was born out of personal necessity — Serenity wanted an app to help with basic self care and everyday mental health struggles. That app didn’t exist, so when they came to us with their vision, we were happy to turn it into a reality.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/3e0cc94a006119b0c1b3ee4806dd39b37f16ae70-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;840&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Whether you’re having a stressful day at work or you’re trying to relax at home, getting a notification that says “Breathe; it’ll be okay” might be exactly what you need to get through the day (or even just the hour). Aloe Bud makes it easy to set up reminders like this to remind you to do all kinds of activities, like drinking water, reaching out to talk to a friend, taking your medication, and getting up to move around.&lt;/p&gt;&lt;h3&gt;How we built it&lt;/h3&gt;&lt;p&gt;From the beginning, Aloe Bud was built to be as simple and straightforward as possible, with the ability to expand and customize it in the future. This is why it’s written with the latest version of Swift 4.1 for iOS 11.&lt;/p&gt;&lt;p&gt;Aloe Bud works almost completely offline with local notifications that only users can set up, and all user data is stored on-device only, taking advantage of Core Data for persistence. There’s no need to sign up for an account or connect Aloe Bud to your social accounts—this keeps users in control of driving their own self care experience, allowing us to lower the barrier to entry when it comes to self care. Another added benefit: a user’s data is kept private, only accessible to them.&lt;/p&gt;&lt;p&gt;We are also proud to incorporate open-source work from the iOS community in this app, including extensive use of &lt;a href=&quot;https://github.com/venmo/Static&quot;&gt;Static&lt;/a&gt; for various parts of the UI and &lt;a href=&quot;https://github.com/bizz84/SwiftyStoreKit&quot;&gt;SwiftyStoreKit&lt;/a&gt; for in-app purchases. It would have been far more difficult to complete everything along our timeline without the use of these and other thoroughly-reviewed, well-tested libraries.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2800 2800w, https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=3200 3200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/f3f4aa755e8114b5d46a247f32d33a568f67efe4-3200x1680.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;840&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Now that Aloe Bud has launched, we couldn’t be happier with the reception — the app has been featured in the App Store, &lt;a href=&quot;https://tcrn.ch/2Kjwt0t&quot;&gt;TechCrunch&lt;/a&gt;, and &lt;a href=&quot;https://www.bustle.com/p/aloe-bud-is-the-cutest-self-care-app-youll-ever-see-in-your-dang-life-8909615&quot;&gt;Bustle&lt;/a&gt;, and has received lots of positive feedback online. We’re glad you like it as much as we do! 😊&lt;/p&gt;&lt;p&gt;We were also lucky to work with such a talented team of people. Making the app wouldn’t have been possible without the hard work of all of these fine folks:&lt;/p&gt;&lt;blockquote&gt;🌙 pixel art &lt;a href=&quot;https://twitter.com/galactic_castle?ref_src=twsrc%5Etfw&quot;&gt;@galactic_castle&lt;/a&gt;&lt;br&gt;🖌 design &lt;a href=&quot;https://twitter.com/tinkadoic?ref_src=twsrc%5Etfw&quot;&gt;@tinkadoic&lt;/a&gt;&lt;br&gt;🎶 sounds &lt;a href=&quot;https://twitter.com/Drainpuppet?ref_src=twsrc%5Etfw&quot;&gt;@Drainpuppet&lt;/a&gt;&lt;br&gt;📝 copywriting &lt;a href=&quot;https://twitter.com/jersing?ref_src=twsrc%5Etfw&quot;&gt;@jersing&lt;/a&gt;&lt;br&gt;📱 dev &lt;a href=&quot;https://x.com/serenitydiscko&quot;&gt;@serenitydiscko&lt;/a&gt; &amp;amp; &lt;a href=&quot;https://twitter.com/lickability?ref_src=twsrc%5Etfw&quot;&gt;@lickability&lt;/a&gt;&lt;br&gt;🤓 research &lt;a href=&quot;https://twitter.com/jeansgallo?ref_src=twsrc%5Etfw&quot;&gt;@jeansgallo&lt;/a&gt;&lt;br&gt;🙏 sincere thanks to 1,538 Kickstarter backers who brought this simple idea to life! 🌱 &lt;a href=&quot;https://t.co/ULTIkM5pCk&quot;&gt;https://t.co/ULTIkM5pCk&lt;/a&gt;&lt;br&gt;&lt;br&gt;— Aloe Bud ♡ (@aloebud) &lt;a href=&quot;https://twitter.com/aloebud/status/989966993989230593?ref_src=twsrc%5Etfw&quot;&gt;April 27, 2018&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;Aloe Bud is available to &lt;a href=&quot;https://itunes.apple.com/app/apple-store/id1318382054?mt=8&quot;&gt;&lt;strong&gt;download&lt;/strong&gt;&lt;/a&gt; on the App Store. Try it out and let us know what you think by leaving a review or @-ing us on &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;Twitter&lt;/a&gt;.&lt;/p&gt;&lt;aside class=&quot;p-fl-xs bg-ruby/5 rounded-lg border-ruby border-[1px] text-pretty relative prose-p:first-of-type:mt-0 prose-p:last-of-type:mb-0&quot;&gt; &lt;img class=&quot;h-16 absolute -top-4 left-[102%] opacity-50 hidden sm:block&quot; src=&quot;/doodles/arrows/Arrow_04.svg&quot; alt=&quot;&quot; aria-hidden=&quot;true&quot; inert&gt; &lt;p&gt;Need some assistance making or updating an app? &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Contact us&lt;/a&gt; and we’d love to chat about how we can help.&lt;/p&gt;  &lt;/aside&gt; </content:encoded><author>Jillian Meehan</author></item><item><title>🐞 Insidious Bugs #1: Today Extensions</title><link>https://lickability.com/blog/insidious-bugs-1-today-extensions/</link><guid isPermaLink="true">https://lickability.com/blog/insidious-bugs-1-today-extensions/</guid><description>This app could not be installed at this time.</description><pubDate>Mon, 16 Apr 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today Lickability is relaunching Insidious Bugs on our blog. Insidious Bugs is an occasional series in which we share stories of overcoming obscure issues in iOS development with the goal of saving you the time we lost, should you encounter the issues yourself.&lt;/p&gt;&lt;h3&gt;The Bug&lt;/h3&gt;&lt;p&gt;In this first entry, we conquer an instance of Xcode’s friendly “This app could not be installed at this time” message. We encountered this when attempting to run our project that includes a &lt;a href=&quot;https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/Today.html&quot;&gt;Today Extension&lt;/a&gt;, or widget, in the iOS Simulator.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/603479ff7d79ce0e0c9f916506a731be378eec23-2550x1620.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/603479ff7d79ce0e0c9f916506a731be378eec23-2550x1620.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/603479ff7d79ce0e0c9f916506a731be378eec23-2550x1620.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/603479ff7d79ce0e0c9f916506a731be378eec23-2550x1620.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/603479ff7d79ce0e0c9f916506a731be378eec23-2550x1620.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/603479ff7d79ce0e0c9f916506a731be378eec23-2550x1620.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/603479ff7d79ce0e0c9f916506a731be378eec23-2550x1620.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2550 2550w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/603479ff7d79ce0e0c9f916506a731be378eec23-2550x1620.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1016&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Uhhhhhhh… okay?&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;The Solution&lt;/h3&gt;&lt;p&gt;Would you believe that all it took to solve this issue was this seemingly useless file? Well, &lt;em&gt;that&lt;/em&gt; and many, many hours of failed attempts.&lt;/p&gt;&lt;h3&gt;How We Got Here&lt;/h3&gt;&lt;p&gt;“This app could not be installed at this time” doesn’t give us a lot to work with, but let’s explore the somewhat unique setup that got us here.&lt;/p&gt;&lt;p&gt;We recently added Today Extensions to multiple projects, which all share the same layout. We tend to keep things &lt;a href=&quot;https://en.wikipedia.org/wiki/Don%27t_repeat_yourself&quot;&gt;DRY&lt;/a&gt; (don’t repeat yourself), so we made our approach generic and reusable across multiple projects via a shared framework. In our framework, the Today Extension displays an arbitrary number of cells that have a thumbnail image and two labels. The framework doesn’t care what type of content is to be displayed, whether it’s social media posts, news, reminders, etc.—it simply formats and displays the data written to the shared container by the host app in the exact same way. Here’s an example:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/979fbfcaf4fa0cb3452d108e78bef2f443cb21f5-1125x1950.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/979fbfcaf4fa0cb3452d108e78bef2f443cb21f5-1125x1950.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/979fbfcaf4fa0cb3452d108e78bef2f443cb21f5-1125x1950.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/979fbfcaf4fa0cb3452d108e78bef2f443cb21f5-1125x1950.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/979fbfcaf4fa0cb3452d108e78bef2f443cb21f5-1125x1950.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;1950&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;You’d buy this app, right?&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Hooray! This will save us time and tedious boilerplate as we add our Today Extensions to our projects. We simply need to add the framework and associate the view controller in &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Main​Interface.storyboard&lt;/code&gt; (part of the Today Extension’s target) with the one found in the framework. At least that’s what we thought. Everything compiled just fine, but we couldn’t debug our app. &lt;em&gt;“This app could not be installed at this time.”&lt;/em&gt; 🤔 Since Xcode’s message didn’t point us in any particular direction on how to resolve the bug, we searched log files for anything that was amiss. Every time we’d attempt to build and run, a message similar to the following was printed to &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;mobile\_installation.log.0&lt;/code&gt; within the simulator’s logs folder:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;text&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;err&gt; (0x700008983000) -[MIExecutableBundle makeExecutableWithError:]: 1162: Failed to chmod /Users/Michael/Library/Developer/CoreSimulator/Devices/51417E58-232D-45D5-9DAB-EB4C6F0AA301/data/Library/Caches/com.apple.containermanagerd/Bundle/Application/0D7DF887-D2D6-45F7-86DB-AACE8F2AE875/LickabilityTweets.app/PlugIns/TodayExtension.appex/TodayExtension : No such file or directory (NSPOSIXErrorDomain:2 (null)) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;This error was more descriptive, but still didn’t lead us to a solution. “Why wouldn’t the app extension exist on disk after a build succeeded?” we asked, before making dozens of trial and error attempts across several hours to no avail.&lt;/p&gt;&lt;p&gt;Eventually, we found that moving the view controller back to the Today Extension target would resolve the issue, but we weren’t satisfied with duplicating that file across multiple projects. In a stroke of genius (read: luck) we asked each other, “Wait… is it the fact that the view controller needs to be a part of the extension target, or is it just that we linked &lt;em&gt;something&lt;/em&gt; to the target that made the difference?” After all, our “Compile Sources” build phase required &lt;em&gt;nothing,&lt;/em&gt; since by design, any executable code used by the widget would live in our shared framework.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/ca3a0de45cd49f3c300dcbe0b0386026c9017993-902x298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=226 226w, https://cdn.sanity.io/images/nkt6o869/production/ca3a0de45cd49f3c300dcbe0b0386026c9017993-902x298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=451 451w, https://cdn.sanity.io/images/nkt6o869/production/ca3a0de45cd49f3c300dcbe0b0386026c9017993-902x298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=677 677w, https://cdn.sanity.io/images/nkt6o869/production/ca3a0de45cd49f3c300dcbe0b0386026c9017993-902x298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=902 902w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/ca3a0de45cd49f3c300dcbe0b0386026c9017993-902x298.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=902&quot; width=&quot;902&quot; height=&quot;298&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;What a lonely target 😢&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;So we just added &lt;em&gt;something&lt;/em&gt; to our target to see if it made any difference. A single Swift file without any code.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/ef869773c9adb521f8730589ec60663efbf0dca4-1125x495.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/ef869773c9adb521f8730589ec60663efbf0dca4-1125x495.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/ef869773c9adb521f8730589ec60663efbf0dca4-1125x495.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/ef869773c9adb521f8730589ec60663efbf0dca4-1125x495.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/ef869773c9adb521f8730589ec60663efbf0dca4-1125x495.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;495&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;😠😡😭&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;We thought we had it. But at least we could build &lt;em&gt;and run&lt;/em&gt; now. On launch of the Today Extension target, Xcode’s console would display the following:&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;text&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Unknown class \_TtC19SharedExtensionCode19TodayViewController in Interface Builder file. &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;It seemed as though despite the fact that we were selecting the appropriate view controller class from the appropriate framework within &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Main​Interface.storyboard&lt;/code&gt;, our class was still nowhere to be found at run time. After much more trial and error, we added a bit more code to &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Fix​Empty​Target.swift&lt;/code&gt;. We wanted to make sure everything was properly linked. We imported our shared framework into the file to see if it built (it did) and wrote some test code to see if we could actually make reference to the view controller class (we could). 🧐 “How come we can reference this view controller from a different framework in code, but not in a storyboard?” we pondered. We tried running this, just to see if anything more helpful would print to the console with our reference to the disappearing view controller in code, and…&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/67a1e3f0a8c13dabb0e77ddde1642911f251eb93-1125x1248.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=281 281w, https://cdn.sanity.io/images/nkt6o869/production/67a1e3f0a8c13dabb0e77ddde1642911f251eb93-1125x1248.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/67a1e3f0a8c13dabb0e77ddde1642911f251eb93-1125x1248.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=844 844w, https://cdn.sanity.io/images/nkt6o869/production/67a1e3f0a8c13dabb0e77ddde1642911f251eb93-1125x1248.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125 1125w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/67a1e3f0a8c13dabb0e77ddde1642911f251eb93-1125x1248.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1125&quot; width=&quot;1125&quot; height=&quot;1248&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Whaaaaaaaaaaaaaaaaaaaaat 🤯&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;blockquote&gt;&lt;strong&gt;Michael:&lt;/strong&gt; That… did it?&lt;br&gt;&lt;strong&gt;Andrew:&lt;/strong&gt; It’s time to relaunch Insidious Bugs.&lt;/blockquote&gt;&lt;p&gt;Simply referencing our view controller in code and adding an otherwise useless file to our target solved this mysterious series of problems, and now we finally have our Today Extension framework. The full, documented version of &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Fix​Empty​Target.swift&lt;/code&gt; looks like the following:&lt;/p&gt;&lt;p&gt;Bananas, right? We sincerely hope you don’t encounter an issue like this in your travels. And if you do, we hope this post helped you get back up and running quickly!&lt;/p&gt;&lt;h3&gt;Radars&lt;/h3&gt;&lt;p&gt;Here are the bugs we’ve filed for Apple about these issues:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;http://www.openradar.me/radar?id=4983936539164672&quot;&gt;Today Extension with no linked files fails to install in the simulator (39237943, closed as intended behavior)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;http://www.openradar.me/radar?id=5035739347681280&quot;&gt;Follow-up: Difficult to understand error message with today extension that has no linked files (39974757)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;View controller referenced only from a storyboard not found at runtime &lt;a href=&quot;http://www.openradar.me/radar?id=4952377790562304&quot;&gt;(39238390, Closed as duplicate of 4691676)&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; </content:encoded><author>Michael Liberatore</author></item><item><title>Tools of the Trade</title><link>https://lickability.com/blog/tools-of-the-trade/</link><guid isPermaLink="true">https://lickability.com/blog/tools-of-the-trade/</guid><description>A love letter to our favorite apps</description><pubDate>Thu, 22 Mar 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Running a small software company can be daunting — what are you supposed to do? Where do you start? How do you get help? Our company, &lt;a href=&quot;https://lickability.com&quot;&gt;Lickability&lt;/a&gt;, wouldn’t exist today without a few (okay, more than a few) sharp tools at our disposal to make our lives easier.&lt;/p&gt;&lt;p&gt;Some of our favorites—Google Apps, Xcode, and AppFigures, for example—go all the way back to when Lickability was born in 2009. The more recent additions we can’t live without are Danger, SwiftLint, and 1Password for Teams, which we’ve picked up in the last year. The list only continues to grow.&lt;/p&gt;&lt;p&gt;We’ve dedicated lots of time to researching and testing various applications and services that help us do what we do, and we want to share them with you. Consider this a love letter to all the apps we couldn’t live without.&lt;/p&gt;&lt;h3&gt;Code&lt;/h3&gt;&lt;p&gt;We are a software company, so (surprise!) a lot of the apps we use are designed to make development go as smoothly as possible — from designing to coding to shipping.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/4b93e1381ede1a1139241c33cdf25dfb8478e417-1200x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w, https://cdn.sanity.io/images/nkt6o869/production/4b93e1381ede1a1139241c33cdf25dfb8478e417-1200x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/4b93e1381ede1a1139241c33cdf25dfb8478e417-1200x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=900 900w, https://cdn.sanity.io/images/nkt6o869/production/4b93e1381ede1a1139241c33cdf25dfb8478e417-1200x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/4b93e1381ede1a1139241c33cdf25dfb8478e417-1200x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200&quot; width=&quot;1200&quot; height=&quot;500&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Xcode + Sketch&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;We &lt;em&gt;literally&lt;/em&gt; wouldn’t be able to do what we do without &lt;strong&gt;Xcode&lt;/strong&gt;. It’s got everything you need to make great iOS apps, so it’s really no surprise that it’s first on our list. Xcode has been with us since the very beginning, and it will (hopefully) be around for a long time. It really is the backbone of our work.&lt;/p&gt;&lt;p&gt;Of course, making great apps isn’t just about writing code — apps need design to come to life. &lt;strong&gt;Sketch&lt;/strong&gt; is our go-to tool for making the design documents that later become beautiful apps.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://www.google.com/url?sa=t&amp;rct=j&amp;q=&amp;esrc=s&amp;source=web&amp;cd=2&amp;cad=rja&amp;uact=8&amp;ved=0ahUKEwjT08_1nOzZAhVE_oMKHdfJA1wQFgguMAE&amp;url=https%3A%2F%2Fdeveloper.apple.com%2Fxcode%2F&amp;usg=AOvVaw28WTg1BlCGAf937pcwXMip&quot;&gt;&lt;strong&gt;Xcode&lt;/strong&gt;&lt;/a&gt;: For building and maintaining apps like &lt;a href=&quot;https://itunes.apple.com/us/app/pinpoint-screenshot-editor/id669858907?mt=8&quot;&gt;Pinpoint&lt;/a&gt; and &lt;a href=&quot;http://acceleratorapp.com&quot;&gt;Accelerator&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com&quot;&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/a&gt;: For collaborative development, version control, and code review.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.git-tower.com/mac/&quot;&gt;&lt;strong&gt;Tower&lt;/strong&gt;&lt;/a&gt;: For using git, but even easier than the command-line.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.google.com/url?sa=t&amp;rct=j&amp;q=&amp;esrc=s&amp;source=web&amp;cd=1&amp;cad=rja&amp;uact=8&amp;ved=0ahUKEwjCm4uHnezZAhWL6IMKHa69CqQQFggnMAA&amp;url=https%3A%2F%2Fgithub.com%2Frealm%2FSwiftLint&amp;usg=AOvVaw2wYDH76Z7-QFiV30y4Mvro&quot;&gt;&lt;strong&gt;SwiftLint&lt;/strong&gt;&lt;/a&gt;: For writing clean code and sticking to our style guide.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/danger&quot;&gt;&lt;strong&gt;Danger&lt;/strong&gt;&lt;/a&gt;: For preventing us from making silly mistakes in pull requests.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.buddybuild.com&quot;&gt;&lt;strong&gt;Buddybuild&lt;/strong&gt;&lt;/a&gt;**: For easy-to-control build deployment and testing.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://paw.cloud&quot;&gt;&lt;strong&gt;Paw&lt;/strong&gt;&lt;/a&gt;: For testing and learning about APIs we interact with.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://kapeli.com/dash&quot;&gt;&lt;strong&gt;Dash&lt;/strong&gt;&lt;/a&gt;: For faster and easier lookups in Apple’s documentation.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.charlesproxy.com&quot;&gt;&lt;strong&gt;Charles Proxy&lt;/strong&gt;&lt;/a&gt;: For inspecting the network traffic of our apps.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://macromates.com&quot;&gt;&lt;strong&gt;TextMate&lt;/strong&gt;&lt;/a&gt;: For when all you need is a reliable text editor that works.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.sketchapp.com&quot;&gt;&lt;strong&gt;Sketch&lt;/strong&gt;&lt;/a&gt;: For drawing pixel-perfect icons and screens for our apps.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://zeplin.io&quot;&gt;&lt;strong&gt;Zeplin&lt;/strong&gt;&lt;/a&gt;: For collaborative design and delivering specifications.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://xscopeapp.com&quot;&gt;&lt;strong&gt;xScope&lt;/strong&gt;&lt;/a&gt;: For measuring and perfecting iOS graphics.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Customers&lt;/h3&gt;&lt;p&gt;Communicating with customers and clients is a crucial part of any business. These are a few of our favorite ways to keep in touch with &lt;em&gt;you&lt;/em&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d70e499d526f92d4392b0abfaaa8dda2b94d4a3a-1200x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w, https://cdn.sanity.io/images/nkt6o869/production/d70e499d526f92d4392b0abfaaa8dda2b94d4a3a-1200x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/d70e499d526f92d4392b0abfaaa8dda2b94d4a3a-1200x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=900 900w, https://cdn.sanity.io/images/nkt6o869/production/d70e499d526f92d4392b0abfaaa8dda2b94d4a3a-1200x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d70e499d526f92d4392b0abfaaa8dda2b94d4a3a-1200x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200&quot; width=&quot;1200&quot; height=&quot;500&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Harvest + Xero&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;&lt;strong&gt;Harvest&lt;/strong&gt; powers all of our work by keeping track of how we spend our time, every hour of every day. It keeps us on task and keeps our clients in the loop about what we’re working on, while also handling all of our estimates and invoices. Harvest also automatically syncs our invoices with &lt;strong&gt;Xero&lt;/strong&gt;, the bookkeeping software that helps us (and our accountant) check in on our finances.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://www.getharvest.com/?chan=Google&amp;device=c&amp;kw=harvest%20app&amp;loc=9060351&amp;camp=258020009&amp;adgr=17736737009&amp;gclid=EAIaIQobChMIyPSNsJ3s2QIV3oWzCh1J2QWYEAAYASAAEgJgRfD_BwE&quot;&gt;&lt;strong&gt;Harvest&lt;/strong&gt;&lt;/a&gt;: For keeping track of time spent working on projects.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.xero.com/us/&quot;&gt;&lt;strong&gt;Xero&lt;/strong&gt;&lt;/a&gt;: For keeping track of invoices, expenses, and revenue.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://appfigures.com&quot;&gt;&lt;strong&gt;AppFigures&lt;/strong&gt;&lt;/a&gt;: For tracking app sales and finding out when we’re featured.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.helpshift.com&quot;&gt;&lt;strong&gt;Helpshift&lt;/strong&gt;&lt;/a&gt;: For delivering better customer support.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://mailchimp.com/brain/#/&quot;&gt;&lt;strong&gt;MailChimp&lt;/strong&gt;&lt;/a&gt;: For sending newsletters to keep you up to date with what we’re working on.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://medium.com&quot;&gt;&lt;strong&gt;Medium&lt;/strong&gt;&lt;/a&gt;: For writing and publishing this blog. 😎&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Communication&lt;/h3&gt;&lt;p&gt;And, of course, internal communication is what keeps Lickability running. We like to use tools that make it as easy as possible to keep up to date with everyone at the company and what they’re working on.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/23c578e1378c8e61676bae2ea1bb92dd5fba1f64-1200x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w, https://cdn.sanity.io/images/nkt6o869/production/23c578e1378c8e61676bae2ea1bb92dd5fba1f64-1200x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/23c578e1378c8e61676bae2ea1bb92dd5fba1f64-1200x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=900 900w, https://cdn.sanity.io/images/nkt6o869/production/23c578e1378c8e61676bae2ea1bb92dd5fba1f64-1200x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/23c578e1378c8e61676bae2ea1bb92dd5fba1f64-1200x500.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200&quot; width=&quot;1200&quot; height=&quot;500&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Slack + Google Apps&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;&lt;strong&gt;Slack&lt;/strong&gt; is our hub; it’s the glue that holds us all together. Populated by our entire team, plus a few people we love to chat with during the day, the Lickability Slack is a one-stop shop for asking questions, giving feedback, and making fun of each other’s tweets.&lt;/p&gt;&lt;p&gt;Some of our most-used tools on a daily basis are &lt;strong&gt;Google Apps&lt;/strong&gt; like Gmail, Calendar, Hangouts, and Drive. Having all of our files, schedules, and conversations in one easily accessible place helps keeps us organized like you wouldn’t believe.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://slack.com&quot;&gt;&lt;strong&gt;Slack&lt;/strong&gt;&lt;/a&gt;: For giving us a space to talk to (and joke with) each other.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://basecamp.com&quot;&gt;&lt;strong&gt;Basecamp&lt;/strong&gt;&lt;/a&gt;: For keeping us on top of our to-dos and checkins.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://gsuite.google.com&quot;&gt;&lt;strong&gt;Google Apps&lt;/strong&gt;&lt;/a&gt; (Gmail, Calendar, Hangouts, Drive): For keeping track of &lt;em&gt;everything&lt;/em&gt; — important files, dates, and conversations.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;http://www.bear-writer.com&quot;&gt;&lt;strong&gt;Bear&lt;/strong&gt;&lt;/a&gt;: For taking beautiful Markdown notes.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://justworks.com&quot;&gt;&lt;strong&gt;Justworks&lt;/strong&gt;&lt;/a&gt;: For simplifying the payroll and benefits process.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://recruitee.com/en&quot;&gt;&lt;strong&gt;Recruitee&lt;/strong&gt;&lt;/a&gt;: For organizing the hiring process.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;http://1password&quot;&gt;&lt;strong&gt;1Password for Teams&lt;/strong&gt;&lt;/a&gt;: For locking down and sharing all our team logins.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://giphy.com&quot;&gt;&lt;strong&gt;Giphy&lt;/strong&gt;&lt;/a&gt;/&lt;a href=&quot;http://gifbrewery.com&quot;&gt;&lt;strong&gt;GIFBrewery&lt;/strong&gt;&lt;/a&gt;: For making gifs! Enough said.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Although these are the tools that have worked best for us the past few years, we’re always looking for better ways to serve our clients and customers. So if you’ve got the inside scoop on an app or service you think we’re missing out on, let us know! You can reach out to us on Twitter at &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;@lickability&lt;/a&gt;. 💌&lt;/p&gt; </content:encoded><author>Jillian Meehan</author></item><item><title>How We Smoke Test Pull Requests with Git Revert</title><link>https://lickability.com/blog/how-we-smoke-test-pull-requests-with-git-revert/</link><guid isPermaLink="true">https://lickability.com/blog/how-we-smoke-test-pull-requests-with-git-revert/</guid><description>One weird trick</description><pubDate>Mon, 26 Feb 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;You’ve been there&lt;/em&gt;: you get assigned to review a pull request for a work-in-progress feature you know little about, and you have trouble seeing how it all fits together with the work that’s yet to come. The code looks good, but you’re hesitant to add your stamp of approval and hit the merge button. Even with thorough UI and unit tests, you want to test the new feature and see it in action yourself, just as the author did during development. Rather than making the reviewer follow a complex set of instructions to add code or print statements to verify that the in-progress feature is working, we’ve landed on this &lt;em&gt;one weird trick&lt;/em&gt;. Internally, we call it the “git revert trick.”&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/a4b9ebca81ad15f6a981cbad9a5052a21189084d-1570x1874.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=393 393w, https://cdn.sanity.io/images/nkt6o869/production/a4b9ebca81ad15f6a981cbad9a5052a21189084d-1570x1874.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=785 785w, https://cdn.sanity.io/images/nkt6o869/production/a4b9ebca81ad15f6a981cbad9a5052a21189084d-1570x1874.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1178 1178w, https://cdn.sanity.io/images/nkt6o869/production/a4b9ebca81ad15f6a981cbad9a5052a21189084d-1570x1874.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1570 1570w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/a4b9ebca81ad15f6a981cbad9a5052a21189084d-1570x1874.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1570&quot; width=&quot;1570&quot; height=&quot;1874&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;An example of the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;git revert&lt;/code&gt; trick in action!&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;How It Works&lt;/h3&gt;&lt;p&gt;When a developer is unable to provide clear “How to Test” steps without requiring additional complicated changes, they can now commit and revert the test code that they used to initially &lt;a href=&quot;https://en.wikipedia.org/wiki/Smoke_testing_%28software%29&quot;&gt;smoke test&lt;/a&gt; their feature and provide the git command necessary to re-stage the test code in the reviewer’s working copy. The command is &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;git revert -n &amp;lt;revert-test-code-sha&amp;gt;&lt;/code&gt;. This reverts the &lt;em&gt;removal&lt;/em&gt; of the test code, with &lt;a href=&quot;https://git-scm.com/docs/git-revert#Documentation/git-revert.txt--n&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;-n&lt;/code&gt;&lt;/a&gt; (short for &lt;a href=&quot;https://git-scm.com/docs/git-revert#Documentation/git-revert.txt---no-commit&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;--no-commit&lt;/code&gt;&lt;/a&gt;) preventing the revert from being committed, allowing the user to see the working copy changes and easily discard them when they’ve finished testing.&lt;/p&gt;&lt;p&gt;We now frequently make use of this command in our pull request reviews. It removes room for error in the reviewer’s setup process, provides in-context and diff-able code for exercising the changes, and prevents any throwaway test code for in-progress features from ever getting merged.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/Lickability/PinpointKit/pull/177&quot;&gt;&lt;strong&gt;See it in action&lt;/strong&gt;&lt;/a&gt; in our open source library PinpointKit and let us know what you think of this technique here or on Twitter at &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;&lt;strong&gt;@lickability&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt; </content:encoded><author>Michael Liberatore</author></item><item><title>Shelf: A Retrospective</title><link>https://lickability.com/blog/shelf-a-retrospective/</link><guid isPermaLink="true">https://lickability.com/blog/shelf-a-retrospective/</guid><description>Why we didn&apos;t ship</description><pubDate>Thu, 01 Feb 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It’s June 5th, 2017. WWDC’s Keynote is in progress, and the Lickability team is spread across the auditorium in San Jose and our office in NYC when Apple unveils the iOS 11 updates coming to iPad. We are instantly excited about drag and drop.&lt;/p&gt;&lt;p&gt;In the aftermath of all the announcements, we quickly gathered to discuss building something new for the release of iOS 11. We were inspired by Federico Viticci’s &lt;a href=&quot;https://www.macstories.net/stories/ios-11-ipad-wishes-and-concept-video/&quot;&gt;iOS 11 iPad wishes and concept video&lt;/a&gt; and wanted to make the “shelf” concept a reality. During the past summer, Lickability was busy dragging and dropping away to deliver our vision of that concept: Shelf. Though we ultimately chose not to ship the app, we wanted to share with you our journey throughout the process.&lt;/p&gt;&lt;h3&gt;Exploring Shelf&lt;/h3&gt;&lt;p&gt;Shelf was to be a tool for iPad power users to temporarily (or permanently) store and organize groups of items to drag and drop. It would become a scratchpad full of the stuff you’re working on and frequently need to reference or drop into other apps.&lt;/p&gt;&lt;p&gt;By being able to run in full screen, split view, or slide over, Shelf would always be available to use to quickly access items while working. Drag some items in from apps like Photos, Safari, or Maps, and then drag them back out when needed. This would help to reduce time spent looking for that thing you know you sent a few days ago.&lt;/p&gt;&lt;h3&gt;Design and Development Approaches&lt;/h3&gt;&lt;p&gt;From a design and development standpoint, we wanted Shelf to display rich previews of the majority of the item types you would drag and drop. We identified these main categories for that:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Images&lt;/li&gt;&lt;li&gt;Text (Plain or Rich)&lt;/li&gt;&lt;li&gt;PDFs&lt;/li&gt;&lt;li&gt;Map Locations&lt;/li&gt;&lt;li&gt;Contacts&lt;/li&gt;&lt;li&gt;Phone Numbers&lt;/li&gt;&lt;li&gt;Email Addresses&lt;/li&gt;&lt;li&gt;Links&lt;/li&gt;&lt;li&gt;Files&lt;/li&gt;&lt;/ul&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6a65654763d2be1d316b8acbb7bf69dcc171b6da-1030x756.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=258 258w, https://cdn.sanity.io/images/nkt6o869/production/6a65654763d2be1d316b8acbb7bf69dcc171b6da-1030x756.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=515 515w, https://cdn.sanity.io/images/nkt6o869/production/6a65654763d2be1d316b8acbb7bf69dcc171b6da-1030x756.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=773 773w, https://cdn.sanity.io/images/nkt6o869/production/6a65654763d2be1d316b8acbb7bf69dcc171b6da-1030x756.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1030 1030w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6a65654763d2be1d316b8acbb7bf69dcc171b6da-1030x756.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1030&quot; width=&quot;1030&quot; height=&quot;756&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Design mockup of file categories &lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;It was also important to us to support the concept of “stacks” of files that would function like folders when in Shelf and drop as multiple items when dragging out. These two primary features were where we wanted to provide a rich user experience.&lt;/p&gt;&lt;p&gt;We hired a designer, &lt;a href=&quot;https://twitter.com/jereswinnen&quot;&gt;Jeremy Swinnen&lt;/a&gt;, and got to work, focusing on making the data models as generic as possible while maintaining the desire to keep the highest fidelity data.&lt;/p&gt;&lt;p&gt;We watched all of the drag and drop WWDC video sessions and explored all the sample code to figure out the API’s capabilities, navigating API changes and various bugs throughout the process. We were on the bleeding edge, so if something wasn’t working correctly, it was up to us to figure it out — all while trying to make sure Shelf would be a consistent and reliable experience.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/5870294d93cbeec0914c0f9d3dbe3ec5494866fc-1600x1816.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/5870294d93cbeec0914c0f9d3dbe3ec5494866fc-1600x1816.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/5870294d93cbeec0914c0f9d3dbe3ec5494866fc-1600x1816.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/5870294d93cbeec0914c0f9d3dbe3ec5494866fc-1600x1816.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/5870294d93cbeec0914c0f9d3dbe3ec5494866fc-1600x1816.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1816&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Left: Stack concept. Right: Quick Look mock-up.&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;Challenges and Takeaways&lt;/h3&gt;&lt;p&gt;One of the best parts of working on Shelf was for us as a team to take an idea from brainstorming and try to have an app ready for launch day. We now have a deep understanding of the ins and outs of the drag and drop API, as well as many of its quirks, which has made applying this knowledge to our other projects a breeze.&lt;/p&gt;&lt;p&gt;On the flip side, we should have taken a look at the new APIs as a team and had a prototyping day to validate what we thought was achievable on an accelerated timeline. This would have allowed us to better estimate how much time we needed to dedicate to the project and when we needed to be in alpha and beta.&lt;/p&gt;&lt;p&gt;Reaching beta on the later side of “beta season,” we realized that we may not have identified the most useful features. It became clear from the competition that being able to store data in multiple different representations was important—for example, dragging in a markup document and dragging it out as the original file, an image, a rich text file, or plain text.&lt;/p&gt;&lt;p&gt;We ended up with a way to generically store any data in Shelf, at all representations, but this had significant impacts on our architecture rather late in the development cycle. It also took a lot of time away from making sure we had as many rich preview representations of files as possible.&lt;/p&gt;&lt;p&gt;Working with a beta API is always challenging. We found the rough edges of the API, particularly with trying to handle placeholders and various pieces of making “stacks” of items work. There came a point where we were writing a work-around for a work-around, because a particular private file format was broken mid-beta and didn’t get fixed by release.&lt;/p&gt;&lt;h3&gt;Why We Didn’t Ship&lt;/h3&gt;&lt;p&gt;With the late beta of Shelf and the release of iOS 11, we quickly discovered we did not have the right combination of features. While Shelf had one of the best user interfaces, we felt we didn’t have enough of some of our competitors’ major features to justify investing more time. The last thing we wanted to do was release an app we wouldn’t want to invest more time in.&lt;/p&gt;&lt;p&gt;There also were some outstanding issues that hadn’t yet been solved in a way we felt was high-quality enough. For example, there was a point where we could reliably crash our app, as well as all of our competitors’ apps, with a use case we felt was core to the Shelf experience.&lt;/p&gt;&lt;p&gt;We believe apps like Shelf will be vital in moving the platform forward as a productivity device, but the current user base and market doesn’t seem to be large enough for these kind of products just yet. It may be a few more years until enough people are using their iPads daily for “power user”-level productivity.&lt;/p&gt;&lt;p&gt;Rather than release an app that we didn’t feel reached our high standards, we decided to shelve Shelf and instead take it as an incredible learning opportunity for the entire team. In the words of &lt;a href=&quot;https://magicschoolbus.wikia.com/wiki/Ms._Frizzle&quot;&gt;Ms. Frizzle&lt;/a&gt;, &lt;strong&gt;“Take chances, make mistakes, and get messy.”&lt;/strong&gt;&lt;/p&gt; </content:encoded><author>Michael Amundsen</author></item><item><title>New Year, New Site</title><link>https://lickability.com/blog/new-year-new-site/</link><guid isPermaLink="true">https://lickability.com/blog/new-year-new-site/</guid><description>The brand-new Lickability.com</description><pubDate>Wed, 10 Jan 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Two years ago, we launched a simple, single-page website to establish our brand as a consulting and product studio. The new site is completely redesigned and revamped. Two years ago, we launched a simple, single-page website to establish our brand as a consulting and product studio. Since then, we have grown as a team from just one to seven full-time people, worked with a number of incredible clients, and continued evolving our own products. Our company has lots to be proud of, and our old site just didn’t reflect that — it was time for something new. Say hello to the revamped &lt;a href=&quot;https://lickability.com&quot;&gt;&lt;strong&gt;lickability.com&lt;/strong&gt;&lt;/a&gt; 👋.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/6efbee73707946b898d041f1171d4e87aaf7a54a-1487x1118.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=372 372w, https://cdn.sanity.io/images/nkt6o869/production/6efbee73707946b898d041f1171d4e87aaf7a54a-1487x1118.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=744 744w, https://cdn.sanity.io/images/nkt6o869/production/6efbee73707946b898d041f1171d4e87aaf7a54a-1487x1118.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1115 1115w, https://cdn.sanity.io/images/nkt6o869/production/6efbee73707946b898d041f1171d4e87aaf7a54a-1487x1118.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1487 1487w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/6efbee73707946b898d041f1171d4e87aaf7a54a-1487x1118.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1487&quot; width=&quot;1487&quot; height=&quot;1118&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;The brand-new Lickability.com&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;How We Built It&lt;/h3&gt;&lt;blockquote&gt;🚨 I’m looking for a great web designer/developer for a new &lt;a href=&quot;https://twitter.com/lickability?ref_src=twsrc%5Etfw&quot;&gt;@lickability&lt;/a&gt; project.&lt;br&gt;&lt;br&gt;DM me if you have a recommendation (person or company)! 🚨&lt;br&gt;&lt;br&gt;— mb Bischoff (@mb) &lt;a href=&quot;https://twitter.com/mb/status/875750375579676672?ref_src=twsrc%5Etfw&quot;&gt;June 16, 2017&lt;/a&gt;&lt;/blockquote&gt;&lt;p&gt;In June, we put out a call for web designers and developers who could help with the site. After initial calls and meetings with lots of talented folks, we settled down to start working with the &lt;a href=&quot;https://pickaxe.nyc&quot;&gt;Pickaxe&lt;/a&gt; team in September, along with copywriter &lt;a href=&quot;https://www.anniemaguire.com&quot;&gt;Annie Maguire&lt;/a&gt; and photographer &lt;a href=&quot;https://jorgeq.com&quot;&gt;Jorge Quinteros&lt;/a&gt;. Together, we built a site that is hosted on &lt;a href=&quot;https://pages.github.com&quot;&gt;GitHub Pages&lt;/a&gt;, statically compiled with &lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt;, and can be easily developed locally from within a &lt;a href=&quot;https://www.docker.com/what-container&quot;&gt;Docker container&lt;/a&gt;. A few months later, we are ready (and excited!) to share the finished product with you.&lt;/p&gt;&lt;h3&gt;What’s New&lt;/h3&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/164b9aa05f3025409e5156972db364a70cf9ee5e-2408x2252.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/164b9aa05f3025409e5156972db364a70cf9ee5e-2408x2252.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/164b9aa05f3025409e5156972db364a70cf9ee5e-2408x2252.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/164b9aa05f3025409e5156972db364a70cf9ee5e-2408x2252.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/164b9aa05f3025409e5156972db364a70cf9ee5e-2408x2252.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w, https://cdn.sanity.io/images/nkt6o869/production/164b9aa05f3025409e5156972db364a70cf9ee5e-2408x2252.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2400 2400w, https://cdn.sanity.io/images/nkt6o869/production/164b9aa05f3025409e5156972db364a70cf9ee5e-2408x2252.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2408 2408w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/164b9aa05f3025409e5156972db364a70cf9ee5e-2408x2252.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1496&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Before and After&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Our goal for the new website was to have a home for people to learn about who we are and what we do, see the products we’ve worked on, and easily get in touch with us. We wanted the site to showcase our projects and our team with the same care and close attention to detail that we put into our work.&lt;/p&gt;&lt;p&gt;Lickability.com has new pages to &lt;a href=&quot;https://lickability.com/clients&quot;&gt;highlight the work we’ve done&lt;/a&gt; for our clients as well as our &lt;a href=&quot;https://lickability.com/products&quot;&gt;own creations&lt;/a&gt;, like Accelerator and &lt;a href=&quot;https://blog.lickability.com/pinpointkit-676b74e8a196&quot;&gt;PinpointKit&lt;/a&gt;. It also has information for people interested in working with us in any capacity, from details about Lickability’s background and services to our &lt;a href=&quot;https://lickability.com/careers&quot;&gt;Jobs&lt;/a&gt; page. Plus, our new &lt;a href=&quot;https://lickability.com/contact&quot;&gt;Contact&lt;/a&gt; page makes it easier than ever to get in touch with us.&lt;/p&gt;&lt;h3&gt;What’s Next&lt;/h3&gt;&lt;p&gt;We hope the site will give us the platform to expand our offerings as our company continues to grow over the coming months and years. We’ve even got an infrequent &lt;a href=&quot;https://lickability.us2.list-manage.com/subscribe/post?u=8a254564acdd2915e0dd0d719&amp;id=c71805921b&quot;&gt;newsletter&lt;/a&gt; that will keep you updated when we launch some of the new ideas we’re working on.&lt;/p&gt;&lt;p&gt;If you haven’t already, we encourage you to check out the site for yourself and get to know Lickability a little better. If you like what you see or have any feedback for us, we’re only an &lt;a href=&quot;https://lickability.com/contact&quot;&gt;email&lt;/a&gt; or a &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;tweet&lt;/a&gt; away.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Huge thanks to the team at Pickaxe, Annie Maguire, and Jorge Quinteros for helping us build the website of our dreams. 🙌&lt;/em&gt;&lt;/p&gt; </content:encoded><author>Jillian Meehan</author></item><item><title>A New Look for The Atlantic App</title><link>https://lickability.com/blog/a-new-look-for-the-atlantic-app/</link><guid isPermaLink="true">https://lickability.com/blog/a-new-look-for-the-atlantic-app/</guid><description>A New Look for The Atlantic App</description><pubDate>Tue, 22 Aug 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here at &lt;a href=&quot;http://lickability.com&quot;&gt;Lickability&lt;/a&gt;, we obsess over journalism. Having worked for and with some of the most important &lt;a href=&quot;https://blog.lickability.com/helping-build-the-new-yorker-today-a30b2ab1fffa&quot;&gt;media&lt;/a&gt;&lt;a href=&quot;https://itunes.apple.com/us/app/nytimes-breaking-politics-national-world-news/id284862083?mt=8&quot;&gt;organizations&lt;/a&gt;, we have nurtured a deep respect for thoughtful reporting and opinion. So when &lt;a href=&quot;https://www.theatlantic.com&quot;&gt;The Atlantic&lt;/a&gt; contacted us last year about partnering with them to redesign their iOS app, we were humbled and ecstatic to work with such a &lt;a href=&quot;https://www.washingtonpost.com/lifestyle/style/the-atlantic-is-most-vital-when-america-is-fractured-good-thing-it-soars-today/2017/07/21/11ce818e-6d46-11e7-96ab-5f38140b38cc_story.html?utm_term=.f537168bd427&quot;&gt;vital&lt;/a&gt; publication that excels in providing high quality news and analysis.&lt;/p&gt;&lt;p&gt;In April of this year, we visited The Atlantic’s offices in Washington, D.C. to spend some time with their fantastic team and collaborate on ideas for the new version. During this process, we helped refine an app design that we believe is simple to use and understand while reflecting the sensibilities and history of the publication. The design should also feel right at home when iOS 11 launches, adapting the large title styles of the upcoming operating system.&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/42365c2d3f6a373019cd45577e7c8d396c58bacc-300x300.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=150 150w, https://cdn.sanity.io/images/nkt6o869/production/42365c2d3f6a373019cd45577e7c8d396c58bacc-300x300.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/42365c2d3f6a373019cd45577e7c8d396c58bacc-300x300.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300&quot; width=&quot;300&quot; height=&quot;300&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;From top to bottom, &lt;a href=&quot;https://itunes.apple.com/app/id397599894&quot;&gt;The Atlantic 5.0&lt;/a&gt; is a universal iOS app that is more modern, easier to use, and faster. The app’s speedier tab bar navigation now clearly organizes the content into top stories, sections, and magazines. Magazines are displayed with their full cover, and archived issues are now accessible all the way back to 2004. It’s also quicker than ever to save stories and full magazine issues for reading offline and manage those items so they don’t take up too much space.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/b44e9bb56b38bc1dda532dd21ad28affe956798d-2000x1334.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/b44e9bb56b38bc1dda532dd21ad28affe956798d-2000x1334.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/b44e9bb56b38bc1dda532dd21ad28affe956798d-2000x1334.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/b44e9bb56b38bc1dda532dd21ad28affe956798d-2000x1334.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/b44e9bb56b38bc1dda532dd21ad28affe956798d-2000x1334.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/b44e9bb56b38bc1dda532dd21ad28affe956798d-2000x1334.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1067&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Building the App&lt;/h3&gt;&lt;p&gt;We developed this version of The Atlantic using the latest available technologies, exclusively writing it in Swift 3.1 with iOS 10 APIs. The app uses stack views, Auto Layout, adaptivity APIs, and Dynamic Type, allowing readers to set their own size for the app or use the system-wide settings. It is also ready for multitasking, with customized layouts on iPad for each size of the app. Finally, we adhered to programming best practices such as the single responsibility principle and separation of concerns, with small, focused types, and made use of &lt;a href=&quot;https://blog.lickability.com/our-view-on-view-models-4bb1d0675038&quot;&gt;view models&lt;/a&gt; for each view in the application. These techniques provide a solid foundation for continuing to iterate on the product to add exciting features and fix troublesome issues.&lt;/p&gt;&lt;h3&gt;Our Tools&lt;/h3&gt;&lt;p&gt;Nobody builds software in a vacuum. On nearly all of our projects, we have found that &lt;a href=&quot;https://www.buddybuild.com&quot;&gt;Buddybuild&lt;/a&gt; streamlines our continuous integration, distribution, and submission processes. This time especially, Buddybuild made configuring and sending test builds a breeze, so that all of our testers were always up-to-date. We are also proud to incorporate open-source work from the iOS community in this app, including extensive use of &lt;a href=&quot;https://github.com/pinterest/PINCache&quot;&gt;PINCache&lt;/a&gt; for persistence, &lt;a href=&quot;https://github.com/onevcat/Kingfisher&quot;&gt;Kingfisher&lt;/a&gt; for image downloading and caching, and &lt;a href=&quot;https://github.com/tid-kijyun/Kanna&quot;&gt;Kanna&lt;/a&gt; for HTML parsing. It would have been far more difficult to complete everything along our timeline without the use of these thoroughly-reviewed, well-tested libraries, along with many others.&lt;/p&gt;&lt;p&gt;With the small, focused, and determined &lt;a href=&quot;https://www.theatlantic.com/masthead/&quot;&gt;team&lt;/a&gt; at The Atlantic, we were able to assist in bringing &lt;a href=&quot;https://www.theatlantic.com/app/&quot;&gt;this app&lt;/a&gt; from conception to release in 4 months. This timeline would not have been possible without the tireless work of the whole team. We believe that today’s release represents a significant step in The Atlantic’s continued success on digital platforms, and hope that it provides an even better mobile venue for some of the most important journalism in the industry.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://itunes.apple.com/app/id397599894&quot;&gt;&lt;strong&gt;Download the app&lt;/strong&gt;&lt;/a&gt; today to read this essential publication with its brand new look.&lt;/p&gt;&lt;blockquote&gt;Need some assistance making or updating your app? Send us a note at hello@lickability.com and we’d love to chat about how we can help.&lt;/blockquote&gt; </content:encoded><author>Brian Capps</author></item><item><title>Our View on View Models</title><link>https://lickability.com/blog/our-view-on-view-models/</link><guid isPermaLink="true">https://lickability.com/blog/our-view-on-view-models/</guid><description>How to keep your view controllers focused</description><pubDate>Sun, 29 Jan 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Over the past few years, the &lt;a href=&quot;https://www.objc.io/issues/13-architecture/mvvm/&quot;&gt;Model-View-ViewModel&lt;/a&gt; (MVVM) design pattern has become popular in iOS application architecture, battling the &lt;a href=&quot;http://khanlou.com/2015/12/massive-view-controller/&quot;&gt;massive view controller&lt;/a&gt; problem that traditional &lt;a href=&quot;https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html&quot;&gt;Model-View-Controller&lt;/a&gt; (MVC) architecture left us with. Aided by a fourth component, the view model, MVVM offers a means of breaking some presentation logic out of controllers.&lt;/p&gt;&lt;p&gt;View models and their many definitions can leave developers new to the topic scratching their heads. It’s also possible to shift too much responsibility to view models in an effort to make view controllers lighter. We don’t strictly follow MVVM at Lickability, but we do make extensive use of view models, and we think it’s worth sharing exactly how. We’ll explore our approach to using view models, what they are and aren’t in our codebases, and why we find them delightful to work with in iOS and Swift.&lt;/p&gt;&lt;h3&gt;View Model: A Simple Definition&lt;/h3&gt;&lt;p&gt;We broadly define “view model” as &lt;em&gt;a type that encapsulates the data needed to populate a particular kind of view&lt;/em&gt;. More succinctly, a view model is &lt;em&gt;a view’s model&lt;/em&gt;. These basic definitions leave something to be desired. Do our view models merely store data and remain void of any operations? No — we allow them to perform operations strictly in the aid of displaying data, so let’s expand this definition to &lt;em&gt;a type that encapsulates the data needed to populate a particular kind of view and the presentation logic needed to transform the data into properties that can be rendered&lt;/em&gt;. Okay, but what qualifies as “presentation logic,” and what does “transform” mean in this context? 😵&lt;/p&gt;&lt;h3&gt;View Model: A Concrete Example&lt;/h3&gt;&lt;p&gt;Let’s start over, and discuss view models in terms of UIKit and iOS application architecture with an example. Ultimately, we’re going to build a cell that can display a comment about a blog post for an interface that looks like this:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/7afb427dc32ffcb5139dba4aa3ff5435bde6fccb-377x669.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=189 189w, https://cdn.sanity.io/images/nkt6o869/production/7afb427dc32ffcb5139dba4aa3ff5435bde6fccb-377x669.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=377 377w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/7afb427dc32ffcb5139dba4aa3ff5435bde6fccb-377x669.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=377&quot; width=&quot;377&quot; height=&quot;669&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;The completed interface with comment cells backed by view models&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;We’ll start with a view. Specifically a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Table​View​Cell&lt;/code&gt;, which is a &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;UI​View&lt;/code&gt; subclass. A &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;UI​View&lt;/code&gt; defines a rectangle of content and contains some smarts about user input that we’ll deal with later. But hey, that’s visual! What’s it look like?&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d6e3fbde45dfd18ebc8c0a0fdb5d2f4f8fa25598-778x290.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=195 195w, https://cdn.sanity.io/images/nkt6o869/production/d6e3fbde45dfd18ebc8c0a0fdb5d2f4f8fa25598-778x290.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=389 389w, https://cdn.sanity.io/images/nkt6o869/production/d6e3fbde45dfd18ebc8c0a0fdb5d2f4f8fa25598-778x290.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=584 584w, https://cdn.sanity.io/images/nkt6o869/production/d6e3fbde45dfd18ebc8c0a0fdb5d2f4f8fa25598-778x290.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=778 778w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d6e3fbde45dfd18ebc8c0a0fdb5d2f4f8fa25598-778x290.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=778&quot; width=&quot;778&quot; height=&quot;290&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;An empty view in Interface Builder&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Great, we’ve got a view. Our cell is empty and rendered with its default &lt;a href=&quot;https://developer.apple.com/reference/uikit/uiview/1622591-backgroundcolor&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;background​Color&lt;/code&gt;&lt;/a&gt;. We haven’t provided it with any data yet, nor have we exposed any means to give it data. Let’s step up our game and fill the view with UI components. To keep our code snippets focused on the topic at hand, we’ll leave the layout to Interface Builder, but you’re free to set up and lay out the view however you’d like. Here’s the final layout of our cell:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/8df825e6ac6fe871386b9016ec7554356ac70867-830x308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=208 208w, https://cdn.sanity.io/images/nkt6o869/production/8df825e6ac6fe871386b9016ec7554356ac70867-830x308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=415 415w, https://cdn.sanity.io/images/nkt6o869/production/8df825e6ac6fe871386b9016ec7554356ac70867-830x308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=623 623w, https://cdn.sanity.io/images/nkt6o869/production/8df825e6ac6fe871386b9016ec7554356ac70867-830x308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=830 830w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/8df825e6ac6fe871386b9016ec7554356ac70867-830x308.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=830&quot; width=&quot;830&quot; height=&quot;308&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;A designed UITableViewCell in Interface Builder representing a comment posted on a blog&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Awesome, we’ve got some content! Or… placeholders for content. We’ll need to get the data from the model layer of our application into copies of this cell to display on screen. Before our view model will make sense, we need to define that layer of the application.&lt;/p&gt;&lt;h3&gt;The Model&lt;/h3&gt;&lt;p&gt;Let’s look at a simple model type, which mirrors an object returned from a web service. Assume the application has a use for all of this stuff, despite the cell in our example requiring only a subset.&lt;/p&gt;&lt;p&gt;For good measure, here’s the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Comment​Author&lt;/code&gt; type referenced in &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Comment&lt;/code&gt; above.&lt;/p&gt;&lt;p&gt;Look at all that data! In a real application, there may be even more, like the number of “likes,” more author information, and perhaps storage for multimedia included in a comment. But even so, our cell only needs a subset of this data:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The author’s name&lt;/li&gt;&lt;li&gt;The date the comment was posted&lt;/li&gt;&lt;li&gt;The text of the comment&lt;/li&gt;&lt;li&gt;Whether the user has edited the comment’s text since posting it&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Although using a value type to model a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Comment&lt;/code&gt; would save us from our view being able to manipulate any aspect of the application’s model layer, we’re still going to provide the view with only the data it needs. Specifically, our cell subclass will not know about the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Comment&lt;/code&gt; type. We can accomplish this in a number of different ways including:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Exposing the UI components on our cell subclass to be populated with the data from the model&lt;/li&gt;&lt;li&gt;Keeping the UI components private and exposing individual properties for each piece of data that will populate the UI components when set&lt;/li&gt;&lt;li&gt;Expose a single point of configuration: a view model&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Guess which one I’m going to pick.&lt;/p&gt;&lt;p&gt;The first option would greatly increase the surface area of our cell’s API. This could lead to heavy configuration being done at the view controller layer, even spreading or duplicating across multiple view controllers if we chose to use this cell in more than one context. We’d expose the ability to fill the labels with &lt;em&gt;any&lt;/em&gt; text, modify any stylistic attributes, and easily manipulate layout. &lt;em&gt;No thanks&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;The second option, exposing multiple properties, is better but still not great. We wouldn’t run into any major issues with this approach in our simple example. But, more complex views that expose many configuration properties need to concern themselves with the order in which properties are set and whether some or all have values. This can also lead to multiple layout and drawing passes as each property is configured separately instead of a single pass after configuration.&lt;/p&gt;&lt;p&gt;The way we use view models aims to solve the problems of the first two approaches. The view only needs to know about the data it displays, and the view exposes a single property, the view model, such that layout and drawing can always happen predictably and completely. So what might this look like?&lt;/p&gt;&lt;h3&gt;The View and View Model&lt;/h3&gt;&lt;p&gt;We usually tie a view model type directly to a view type by nesting it. This covers most of our use cases and makes naming trivial thanks to namespacing. Let’s take a look at a snippet of our &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Comment​Cell&lt;/code&gt; that backs the view we designed earlier.&lt;/p&gt;&lt;p&gt;We’ve declared two types: &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Comment​Cell&lt;/code&gt; which is our &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;UI​Table​View​Cell&lt;/code&gt; subclass, and &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;Comment​Cell.View​Model&lt;/code&gt;, the view model that backs this cell type. So far, we have one property declared on &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;Comment​Cell&lt;/code&gt;, which is an instance of &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;Comment​Cell.View​Model&lt;/code&gt;. This will ultimately become the single point of configuration.&lt;/p&gt;&lt;p&gt;Notice that in the view model type, most of the properties can easily be mapped from our model later. We don’t want the view controller layer to take on the responsibility of determining how to format and display an author’s name. We’re also packing the author’s name into the same label as the “Edited” demarcation so that it could wrap naturally. This layout is specific to our cell type, and we want to house that logic in our view model. This is an example of &lt;em&gt;presentation logic&lt;/em&gt;, which we alluded to earlier. Similarly, the view controller shouldn’t be responsible for determining how to format the date displayed in our view. Rather, we leave that presentation logic to our view model. We’ll put this presentation logic in an extension on the view model type to separate it from its purely model-like properties.&lt;/p&gt;&lt;p&gt;Burying this presentation logic deep inside of aUIViewController subclass would make it more difficult to test. We tend to leave these computed properties exposed in our view models so that they may be more easily accessed in our test target, without the need to instantiate any &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​View​Controllers&lt;/code&gt; or &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;UI​Views&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;With the context provided by this example, defining a view model as “a view’s model” begins to make more sense. We store only the properties we need to fill our view with data and we provide operations needed to transform that data into something presentable. Let’s put it to use.&lt;/p&gt;&lt;h3&gt;A Single Point of Configuration&lt;/h3&gt;&lt;p&gt;So far we’ve only defined a single property on our cell subclass.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; viewModel: ViewModel? &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;However, we’re not making use of this just yet. Since we’re using this property as our single point of configuration for all displayable data, we’ll want changes to this property to update our view. We make use of the computed properties that contain our presentation logic as follows.&lt;/p&gt;&lt;p&gt;We define a single means of configuring our view. This makes configuration safer since it becomes impossible to only specify a subset of exposed properties, relying only on &lt;a href=&quot;https://developer.apple.com/reference/uikit/uitableviewcell/1623223-prepareforreuse&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;prepare​For​Reuse()&lt;/code&gt;&lt;/a&gt; to reset properties that &lt;em&gt;can&lt;/em&gt; be left unset. Rather, we force our controller to set them all at once. Our example cell is relatively simple, but in more complex cases, the order in which UI components are updated may matter more, and by exposing only one property we can ensure the order of updates is consistent and predictable.&lt;/p&gt;&lt;p&gt;Our &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Table​View​Data​Source&lt;/code&gt; implementation will be relatively simple with this approach. It’s straightforward for us to translate to the view model type provided our actual model at this layer.&lt;/p&gt;&lt;p&gt;We’ve plucked a few properties from our model object, and that’s pretty much it. Our view models only hold onto the data they need to fill out the UI components. They also specify a means of translating that data into something to present on screen, as we did in our &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Comment​Cell.View​Model&lt;/code&gt; extension.&lt;/p&gt;&lt;p&gt;If you’re following closely, you’ll notice that our view model specifies its &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;author​Image&lt;/code&gt; as a &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;UI​Image?&lt;/code&gt;, not a &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;URL&lt;/code&gt;, so translation from our actual model layer for that particular property doesn’t come quite as easily. You’ll notice that in the snippet above, we’re simply passing &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;nil&lt;/code&gt; for &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;author​Image&lt;/code&gt;. 🤔 Also, what happens when you tap that reply button on the cell? Well, nothing yet.&lt;/p&gt;&lt;h3&gt;View models are not a silver bullet against massive view controllers!&lt;/h3&gt;&lt;p&gt;We’ve housed presentation logic in a location that’s far away from a view controller. However, we’ve kept the responsibilities of our view model simple and well-defined.&lt;/p&gt;&lt;p&gt;Let’s talk about the elephant in the blog post. Where are those comment author images coming from?&lt;/p&gt;&lt;h3&gt;Retrieving Data&lt;/h3&gt;&lt;p&gt;Our view model has a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Image?&lt;/code&gt; property, not a &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;URL&lt;/code&gt;. We do this because we &lt;em&gt;do not&lt;/em&gt; want our view model to be responsible for downloading images. A view model is a view’s model (remember?), and you wouldn’t put networking code in your traditional model layer, would you? &lt;em&gt;Would you?&lt;/em&gt;&lt;/p&gt;&lt;p&gt;(Okay, maybe you did in that one project early in your career, but it eventually bit you, right? And you really hope no one ever sees that model object that retrieves its data from the network on the main thread, parses it, and persists it, right?)&lt;/p&gt;&lt;p&gt;So why would our view model be responsible for retrieving images from the network? Or from disk? We don’t believe that it should.&lt;/p&gt;&lt;p&gt;In our example, we passed &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;nil&lt;/code&gt; for the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;author​Image&lt;/code&gt; parameter of our view model’s initializer with the expectation that &lt;em&gt;something&lt;/em&gt; would come along and set the image once its retrieved from the networking or persistence layer. That’s right, we treat networking and persistence as &lt;em&gt;layers&lt;/em&gt; separate from our model, view, view controller, and view model layers. We won’t dive too deeply into this concept as it deserves a blog post of its own, but suffice it to say we inject our view controllers with network and persistence-related dependencies that handle this for us. The important takeaway is that these are &lt;em&gt;not&lt;/em&gt; the responsibilities of a view or view model — we’ve seen this architectural mistake all too often. There are other ways of moving this type of work out of view controllers!&lt;/p&gt;&lt;p&gt;So instead of setting our &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;author​Image&lt;/code&gt; to &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;nil&lt;/code&gt;, we could have our &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;UI​Table​View​Data​Source&lt;/code&gt; ask the persistence layer of our application to retrieve an image for a particular &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;URL&lt;/code&gt;. This would change our configuration from above to something like the following.&lt;/p&gt;&lt;p&gt;The method &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;image(for:)&lt;/code&gt; in this example would return an optional &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;UI​Image&lt;/code&gt; since there’s no guarantee that we’d actually have that image yet. We’d defer downloading of the image, prioritizing display of the data that we already have. We could kick off the downloading of images in &lt;a href=&quot;https://developer.apple.com/reference/uikit/uitableviewdelegate/1614883-tableview&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;table​View(\_:will​Display:for​Row​At:)&lt;/code&gt;&lt;/a&gt; for the cases in which we don’t have them on disk, or perhaps when our local cache has expired and we want to update an existing image already stored on disk. Once we have this image, say, in the completion handler of a network request method, we can simply set it on the cell’s view model as follows.&lt;/p&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;Swift&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;cell.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;viewModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;authorImage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = imageFromNetwork &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;p&gt;Now you might notice that we’ve actually left the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;author​Image&lt;/code&gt; property on &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;Comment​Cell.View​Model&lt;/code&gt; a &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;var&lt;/code&gt; as opposed to a &lt;code index=&quot;7&quot; isInline=&quot;true&quot;&gt;let&lt;/code&gt;. This is great because it allows us to change the value of a displayable property that we expect to change, but setting it still triggers our &lt;code index=&quot;9&quot; isInline=&quot;true&quot;&gt;view​Model&lt;/code&gt; property’s didSet relieving us from any concerns about the order in which UI components are configured. Despite the fact that we’re updating a single property, we still hit our single point of configuration. (Should I create a &lt;a href=&quot;https://smilesoftware.com/textexpander&quot;&gt;TextExpander&lt;/a&gt; snippet for that phrase?)&lt;/p&gt;&lt;p&gt;To belabor the point, our view models &lt;em&gt;do not&lt;/em&gt; touch the networking layer and &lt;em&gt;do not&lt;/em&gt; touch the persistence layer of our application.&lt;/p&gt;&lt;h3&gt;Delegating Action&lt;/h3&gt;&lt;p&gt;Typically, the handling of tapping a cell can be performed by either a storyboard segue or in the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​Table​View​Delegate&lt;/code&gt; method &lt;a href=&quot;https://developer.apple.com/reference/uikit/uitableviewdelegate/1614877-tableview&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;table​View(\_:did​Select​Row​At:)&lt;/code&gt;&lt;/a&gt;, commonly implemented by the UIViewController or type that is used by the &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;UI​View​Controller&lt;/code&gt;. But notice that in addition to being able to tap the cell, we’ve also got a reply button.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/e02924ee672aadba14ef61ac302475cc5bc56bd0-563x221.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=282 282w, https://cdn.sanity.io/images/nkt6o869/production/e02924ee672aadba14ef61ac302475cc5bc56bd0-563x221.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/e02924ee672aadba14ef61ac302475cc5bc56bd0-563x221.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563&quot; width=&quot;563&quot; height=&quot;221&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;So, does the view model take part in this interaction? No. We like to follow the same pattern as cell selection, in that it can done via a storyboard segue if we’ve specified our layout in prototype cells in a storyboard, or by allowing the &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;UI​View​Controller&lt;/code&gt; to handle the interaction. We do not want the view model or view to own any of this responsibility. Let’s walk through the non-storyboard approach.&lt;/p&gt;&lt;p&gt;First, we need to prepare our cell class to be able to intercept the button tap. We’re not going to carry out the button’s action, but rather provide a means for our view controller to do so. We declare a closure to be set externally on our cell and a corresponding action method.&lt;/p&gt;&lt;p&gt;Whether you use an &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;@IB​Action&lt;/code&gt; or register a target/action using &lt;a href=&quot;https://developer.apple.com/reference/uikit/uicontrol/1618259-addtarget&quot;&gt;&lt;code index=&quot;0&quot; isInline=&quot;true&quot;&gt;add​Target(\_:action:for:)&lt;/code&gt;&lt;/a&gt;, you’ll simply have to call the closure when the button is tapped.&lt;/p&gt;&lt;p&gt;All we have left to carry out the action of tapping a &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;Comment​Cell&lt;/code&gt;’s reply button is to set the &lt;code index=&quot;3&quot; isInline=&quot;true&quot;&gt;reply​Button​Tap​Handler&lt;/code&gt;. We do this in the same location that we configured the &lt;code index=&quot;5&quot; isInline=&quot;true&quot;&gt;view​Model&lt;/code&gt; property discussed earlier.&lt;/p&gt;&lt;p&gt;So, the single point of configuration is for all display-related data. We separate actions into their own properties, as it’s not the view model’s responsibility to carry out the handling of user input. These actions could also be handled via delegation, but we prefer closure properties for this type of interaction.&lt;/p&gt;&lt;h3&gt;Be a (View) Model Citizen&lt;/h3&gt;&lt;p&gt;Looking back at our simplest definition — a view model is &lt;em&gt;a view’s model &lt;/em&gt;— the example we’ve illustrated matches this. It also helps to draw very clear barriers about what a view model is &lt;em&gt;not&lt;/em&gt;. With this knowledge, we can lighten some of the load on our view controllers while preventing our view models from taking on too much responsibility.&lt;/p&gt;&lt;p&gt;For more on view models, our friends &lt;a href=&quot;https://twitter.com/cdzombak&quot;&gt;Chris Dzombak&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/khanlou&quot;&gt;Soroush Khanlou&lt;/a&gt; spent episodes &lt;a href=&quot;https://fatalerror.fm/episodes/2016/8/13/2-view-models&quot;&gt;2&lt;/a&gt; and &lt;a href=&quot;https://fatalerror.fm/episodes/2016/8/29/3-view-models-again&quot;&gt;3&lt;/a&gt; of their podcast &lt;a href=&quot;https://fatalerror.fm/&quot;&gt;Fatal Error&lt;/a&gt; on the topic, which include quality coverage of the problems they can solve, the MVVM design pattern as it relates to iOS development, pitfalls, and more. Check out the show notes for these episodes which include links to many more relevant resources. See also Andy Matuschak’s 2015 NSSpain talk &lt;a href=&quot;https://vimeo.com/140037432&quot;&gt;&lt;em&gt;Let’s Play: Refactor the Mega-Controller&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;What’s Next?&lt;/h3&gt;&lt;p&gt;Of course, there’s still more to cover. The approach that we outlined here doesn’t apply to views that are reusable in different scenarios. Our example included a cell for a blog post comment, specifically, but it’s certainly possible that an app would have much simpler cells that could be reused in multiple contexts. In these circumstances, we make use of view model protocols to share common layout behavior for views with varying purposes. We’ll explore this approach in our next post on view models. Stay tuned!&lt;/p&gt; </content:encoded><author>Michael Liberatore</author></item><item><title>Escape from Times Square</title><link>https://lickability.com/blog/escape-from-times-square/</link><guid isPermaLink="true">https://lickability.com/blog/escape-from-times-square/</guid><description>Escape from Times Square</description><pubDate>Sun, 13 Nov 2016 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;strong&gt;mb&lt;/strong&gt;: “Can you get the Uber?”&lt;br&gt;&lt;br&gt;&lt;strong&gt;Andrew&lt;/strong&gt;: “Sure… aghh, never mind…”&lt;br&gt;&lt;br&gt;&lt;strong&gt;mb&lt;/strong&gt;: “Times Square bug?”&lt;br&gt;&lt;br&gt;&lt;strong&gt;Andrew&lt;/strong&gt;: “Yep.”&lt;br&gt;&lt;br&gt;&lt;strong&gt;mb&lt;/strong&gt;: “I can’t believe they haven’t fixed this by now.”&lt;br&gt;&lt;br&gt;&lt;strong&gt;Andrew&lt;/strong&gt;: “How do I work around it again?”&lt;br&gt;&lt;br&gt;&lt;strong&gt;mb&lt;/strong&gt;: “Restart your phone.”&lt;/blockquote&gt;&lt;p&gt;For most of 2016, the Lickability team was plagued with an iOS issue causing all of our devices to report their current locations to be in the center of Times Square, &lt;a href=&quot;https://www.quora.com/Why-do-New-Yorkers-dislike-Times-Square&quot;&gt;New Yorkers’ favorite spot&lt;/a&gt; in the city.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/ed7abe92b95dcd0a8879a3dee0bcda85caceef16-1334x750.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=334 334w, https://cdn.sanity.io/images/nkt6o869/production/ed7abe92b95dcd0a8879a3dee0bcda85caceef16-1334x750.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=667 667w, https://cdn.sanity.io/images/nkt6o869/production/ed7abe92b95dcd0a8879a3dee0bcda85caceef16-1334x750.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1001 1001w, https://cdn.sanity.io/images/nkt6o869/production/ed7abe92b95dcd0a8879a3dee0bcda85caceef16-1334x750.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1334 1334w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/ed7abe92b95dcd0a8879a3dee0bcda85caceef16-1334x750.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1334&quot; width=&quot;1334&quot; height=&quot;750&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Not again…&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;The only consistent workaround was to restart our phones. After conferring with other iOS developers outside of the company, we believed it to be a widespread problem. Researching online left us wondering how this hadn’t been reported to Apple or heavily discussed on forums.&lt;/p&gt;&lt;p&gt;As time went on, frustration grew. Location sharing in Messages and Find My Friends would suggest we spent most of our days waiting in line for last minute discounts on Broadway tickets at &lt;a href=&quot;https://www.tdf.org/nyc/7/TKTS-ticket-booths&quot;&gt;TKTS&lt;/a&gt;. Rather than exploring some of the best restaurants in the world, friends assumed we were waiting patiently outside of Olive Garden with restaurant buzzer in hand. Transit and walking directions were misleading, ordering food from &lt;a href=&quot;https://www.seamless.com&quot;&gt;Seamless&lt;/a&gt; became a hassle, and I frequently found myself shouting “No Foursquare, I’m &lt;em&gt;not&lt;/em&gt; back at &lt;a href=&quot;https://foursquare.com/v/mms-world/4a065273f964a520ec721fe3&quot;&gt;M&amp;amp;M’s world&lt;/a&gt;!” &lt;a href=&quot;https://developer.apple.com/reference/corelocation&quot;&gt;Core Location&lt;/a&gt; had gone from helpful to irritating. After months of dealing with this issue, and continuing to reach out to other developers in desperation, we were reminded of a debugging feature of Xcode to simulate location.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/18777ec96789c131ec5a7d5034bda85e9dcf254a-686x94.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=343 343w, https://cdn.sanity.io/images/nkt6o869/production/18777ec96789c131ec5a7d5034bda85e9dcf254a-686x94.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=686 686w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/18777ec96789c131ec5a7d5034bda85e9dcf254a-686x94.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=686&quot; width=&quot;686&quot; height=&quot;94&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Edit Scheme… → Run → Options&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;This was a great starting point. The only issue was, none of us had used this debugging tool, and it was still happening to us. Which brings us to our next friend:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/0e3fbc647163ba2f3a721720d9aeae18790d85a8-800x55.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=200 200w, https://cdn.sanity.io/images/nkt6o869/production/0e3fbc647163ba2f3a721720d9aeae18790d85a8-800x55.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/0e3fbc647163ba2f3a721720d9aeae18790d85a8-800x55.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=600 600w, https://cdn.sanity.io/images/nkt6o869/production/0e3fbc647163ba2f3a721720d9aeae18790d85a8-800x55.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/0e3fbc647163ba2f3a721720d9aeae18790d85a8-800x55.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800&quot; width=&quot;800&quot; height=&quot;55&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;At some point, one of us had pushed a change to a shared run scheme on a client project to simulate the device or simulator location to “New York, NY”. Those “other iOS developers” who shared in our frustration early on were employees of the client. We had done this to ourselves! Merely running the app using the default scheme in the Xcode project would spread the problem to a device, persisting long after the work day had ended. Finally, Lickability could breathe a collective sigh of relief, share a few laughs, and learn the lesson of not carefully reviewing changes inside of &lt;code index=&quot;1&quot; isInline=&quot;true&quot;&gt;xcshareddata&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Special thanks to &lt;a href=&quot;https://twitter.com/masterjeef&quot;&gt;Jeff Forbes&lt;/a&gt; for pointing us in the right direction to help us escape from Times Square.&lt;/em&gt;&lt;/p&gt; </content:encoded><author>Michael Liberatore</author></item><item><title>The End of Quotebook</title><link>https://lickability.com/blog/the-end-of-quotebook/</link><guid isPermaLink="true">https://lickability.com/blog/the-end-of-quotebook/</guid><description>The End of Quotebook</description><pubDate>Thu, 06 Oct 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In 2011, as three students in a dorm room, we &lt;a href=&quot;https://readwrite.com/2011/03/31/quotebook_an_ios_app_for_all_you_quotation_mavens/&quot;&gt;released the first version&lt;/a&gt; of Quotebook. Since then, tens of thousands of you have used it to keep track of important quotations in your life.&lt;/p&gt;&lt;p&gt;We’ve recently started receiving an increasing number of complaints from customers that the iCloud syncing features have been working less reliably. We’ve poured thousands of hours into developing, testing, and retesting these features over the years as Apple has released new operating system versions and APIs that have broken our implementation. With the upcoming release of iOS 10, Apple has signaled they will be &lt;a href=&quot;https://mjtsai.com/blog/2016/06/17/the-deprecation-of-icloud-core-data&quot;&gt;removing support&lt;/a&gt; for iCloud Core Data. This is the foundational technology upon which Quotebook’s storage and syncing is built.&lt;/p&gt;&lt;p&gt;Unfortunately, no matter how much time and energy we’ve spent, we’ve been unable to consistently deliver the quality and reliability that we expect of our products due to fundamental problems with Apple’s APIs. Quotebook has always been a passion project of ours, but its sales simply cannot justify the time we’d need to continue to support it going forward or to migrate to another data storage system.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Today, we are removing the app from the App Store&lt;/strong&gt;. If you’ve ever purchased Quotebook, you’ll still be able to download it from the App Store’s “Purchased” screen for the foreseeable future. We’re as heartbroken about this removal as we’re sure some of you will be. We’ll be using the additional time and energy to build even better products and focus more on our current and future apps.&lt;/p&gt;&lt;p&gt;If you’ve got data in Quotebook and you’d like to export it for use in another application or system:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Open Quotebook&lt;/li&gt;&lt;li&gt;Tap the settings gear in the top left&lt;/li&gt;&lt;li&gt;Tap &lt;em&gt;Backup &amp;amp; Restore&lt;/em&gt;&lt;/li&gt;&lt;li&gt;Tap &lt;em&gt;Export CSV&lt;/em&gt;&lt;/li&gt;&lt;li&gt;Choose &lt;em&gt;Email&lt;/em&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Your CSV will contain the quotes, authors, sources, tags, dates, and ratings that you’ve collected in the app, and you can import it into a database like &lt;a href=&quot;https://airtable.com&quot;&gt;Airtable&lt;/a&gt;. If you need to export custom author and source descriptions, instead use the “Back Up” button and email the file to yourself. On a Mac, you can open this file in &lt;a href=&quot;https://itunes.apple.com/us/app/xcode/id497799835?mt=12&quot;&gt;Xcode&lt;/a&gt; or &lt;a href=&quot;https://www.fatcatsoftware.com/plisteditpro/&quot;&gt;PlistEdit Pro&lt;/a&gt;, and on Windows in &lt;a href=&quot;https://www.johnwordsworth.com/projects/plist-pad/&quot;&gt;PlistPad&lt;/a&gt; to extract the data. If you run into trouble with this process, email us at &lt;a href=&quot;mailto:support@quotebookapp.com&quot;&gt;support@quotebookapp.com&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Thank you so much for being a customer, &lt;a href=&quot;https://minimalmac.com/post/4233527241/quotebook-a-notebook-for-your-quotes-on-iphone&quot;&gt;supporter&lt;/a&gt;, and friend of Quotebook. We’re really sorry that we cannot continue to develop the app, and we’ve learned a lot from this experience. If you’re interested in speaking with us about taking over development of the app, email &lt;a href=&quot;mailto:hello@lickability.com&quot;&gt;hello@lickability.com&lt;/a&gt;.&lt;/p&gt;&lt;blockquote&gt;“The problem with books is that they end.” &lt;em&gt;–Caroline Kepnes&lt;/em&gt;&lt;/blockquote&gt; </content:encoded><author>Andrew Harrison</author></item><item><title>Making Meetup</title><link>https://lickability.com/blog/making-meetup/</link><guid isPermaLink="true">https://lickability.com/blog/making-meetup/</guid><description>Making Meetup</description><pubDate>Tue, 27 Sep 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In May of 2015, we started working with our friends at &lt;a href=&quot;http://meetup.com&quot;&gt;Meetup&lt;/a&gt; to improve their iOS app. After adding a few features, the team at Meetup began thinking about embarking on a much larger, ambitious redesign of both the app and their entire platform. Today, a little over a year later, we’re excited to share the app we’ve built together.&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/03882e3f5be833b460e8d92d3bb95c123c945f3e-300x286.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=150 150w, https://cdn.sanity.io/images/nkt6o869/production/03882e3f5be833b460e8d92d3bb95c123c945f3e-300x286.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/03882e3f5be833b460e8d92d3bb95c123c945f3e-300x286.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300&quot; width=&quot;300&quot; height=&quot;286&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;&lt;a href=&quot;https://itunes.apple.com/us/app/meetup-groups-near-you-that/id375990038?ls=1&amp;mt=8&quot;&gt;&lt;strong&gt;Meetup 6.0&lt;/strong&gt;&lt;/a&gt; is a ground-up redesign and rewrite. The new app makes it much easier to find, join, and even start your own Meetups right from your phone. The design takes cues from Meetup’s complete rebranding, Apple’s iOS 10, and the feedback from hundreds of beta testers over the past few months. &lt;a href=&quot;https://www.youtube.com/watch?v=t30iD43VJh0&amp;feature=youtu.be&quot;&gt;Check it out&lt;/a&gt; and let us know what you think.&lt;/p&gt;&lt;p&gt;Meetup was an ideal client for Lickability, as a New York company whose goal of getting people together in real life matches our enthusiasm for the iOS community. The monthly &lt;a href=&quot;http://iosirl.com&quot;&gt;iOS IRL&lt;/a&gt; gathering here in New York is one of our favorite events, and meeting up in-person at our office and conferences has been important for our growth as a company. Meetup brings people together in thousands of cities, and we are thrilled to help further realize that mission.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/5f1fe543b0c40521e15290be3d544b336db897ff-900x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=225 225w, https://cdn.sanity.io/images/nkt6o869/production/5f1fe543b0c40521e15290be3d544b336db897ff-900x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=450 450w, https://cdn.sanity.io/images/nkt6o869/production/5f1fe543b0c40521e15290be3d544b336db897ff-900x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=675 675w, https://cdn.sanity.io/images/nkt6o869/production/5f1fe543b0c40521e15290be3d544b336db897ff-900x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=900 900w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/5f1fe543b0c40521e15290be3d544b336db897ff-900x1200.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=900&quot; width=&quot;900&quot; height=&quot;1200&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Our Role&lt;/h3&gt;&lt;p&gt;Our team began by researching and proposing a technical foundation for the app, written entirely in Swift, to improve the safety, speed, and expressiveness of the codebase. Even though the rewrite has nearly all of the features of its predecessor — and some new ones — it’s 30,000 lines of code shorter with many more unit and integration tests.&lt;/p&gt;&lt;p&gt;We used frameworks like &lt;a href=&quot;https://realm.io&quot;&gt;Realm&lt;/a&gt; and &lt;a href=&quot;https://github.com/Alamofire/Alamofire&quot;&gt;Alamofire&lt;/a&gt; to create the persistence and networking infrastructure for the app and new platform features like UIStackView, layout anchors, and storyboard references to build a modern, adaptive iOS app that will remain easy to change as new features are added. As well, services like &lt;a href=&quot;http://slack.com&quot;&gt;Slack&lt;/a&gt; and &lt;a href=&quot;http://buddybuild.com&quot;&gt;Buddybuild&lt;/a&gt; simplified working with the Meetup team remotely and keeping the tests passing.&lt;/p&gt;&lt;p&gt;The project team focused heavily on keeping the codebase consistent, adhering to best practices like the single responsibility principle, and providing detailed code reviews. Patterns like dependency injection, view models, and composition have made the project a joy to work on, even a year after we started.&lt;/p&gt;&lt;h3&gt;Teamwork&lt;/h3&gt;&lt;p&gt;As the year progressed, Lickability helped to interview candidates who joined Meetup as iOS engineers and get them up to speed on the project. During our time working together, we saw Meetup’s internal team grow from a single developer to a tight-knit group of four.&lt;/p&gt;&lt;p&gt;While helping to expand Meetup’s engineering team, we worked hand-in-hand with them on the technical decisions in the project, deciding on design, architecture, and technology in concert. We think the product released today reflects that close collaboration.&lt;/p&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/7a7f9ee426d0022f27589fe0f7aedf1b398019c0-300x95.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=150 150w, https://cdn.sanity.io/images/nkt6o869/production/7a7f9ee426d0022f27589fe0f7aedf1b398019c0-300x95.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300 300w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/7a7f9ee426d0022f27589fe0f7aedf1b398019c0-300x95.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=300&quot; width=&quot;300&quot; height=&quot;95&quot;/&gt;  &lt;/figure&gt; &lt;h3&gt;Thanks!&lt;/h3&gt;&lt;p&gt;Meetup 6.0 was a gargantuan effort worked on by countless people at Meetup and here at Lickability. We’re incredibly grateful to have contributed to the project alongside fantastic folks like Farah Assir, Jason Brennan, Paul Bruneau, Amy Chiu, Michael Curtes, Adaam Hukins, Michael Pace, Yvette Pasqua, Laura Ragone, Fiona Spruill, Kathy Tafel, and so many more. We can’t imagine a better team. ❤️&lt;/p&gt;&lt;h3&gt;What’s Next?&lt;/h3&gt;&lt;p&gt;We’re excited to continue our relationship with Meetup as we bring new features and bugfixes to the redesigned app over the coming months. As always, we’ll also continue to update our existing products, experiment with new ideas, and seek out partners who want to make great software. If you or your team is looking for assistance building, rewriting, or refactoring an app or SDK, don’t hesitate to email us at hello@lickability.com. We’d love to hear from you.&lt;/p&gt; </content:encoded><author>Brian Capps</author></item><item><title>Presenting PinpointKit</title><link>https://lickability.com/blog/presenting-pinpointkit/</link><guid isPermaLink="true">https://lickability.com/blog/presenting-pinpointkit/</guid><description>Presenting PinpointKit</description><pubDate>Thu, 09 Jun 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here at Lickability, when we’re not designing and building apps for our customers and clients, we spend time building internal frameworks. We like to write code that can be shared across all of our apps to make it easier to build great products.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/078b3afd5c6798507dc726b74070a51fe33a0cf7-1979x446.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/078b3afd5c6798507dc726b74070a51fe33a0cf7-1979x446.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/078b3afd5c6798507dc726b74070a51fe33a0cf7-1979x446.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/078b3afd5c6798507dc726b74070a51fe33a0cf7-1979x446.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/078b3afd5c6798507dc726b74070a51fe33a0cf7-1979x446.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1979 1979w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/078b3afd5c6798507dc726b74070a51fe33a0cf7-1979x446.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;361&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;Together with some friends, we spent the last few months extracting the annotation engine from our app &lt;a href=&quot;https://geo.itunes.apple.com/app/apple-store/id669858907?mt=8&amp;at=10l4Vh&amp;ct=pinpointkit-blog&quot;&gt;Pinpoint&lt;/a&gt; into a free, open-source feedback framework for app developers. We’re calling it &lt;a href=&quot;https://github.com/lickability/PinpointKit&quot;&gt;&lt;strong&gt;PinpointKit&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/9a7f1ad84001c65f85ec6fba5b23307aeed70172-1801x1044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/9a7f1ad84001c65f85ec6fba5b23307aeed70172-1801x1044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/9a7f1ad84001c65f85ec6fba5b23307aeed70172-1801x1044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/9a7f1ad84001c65f85ec6fba5b23307aeed70172-1801x1044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/9a7f1ad84001c65f85ec6fba5b23307aeed70172-1801x1044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1801 1801w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/9a7f1ad84001c65f85ec6fba5b23307aeed70172-1801x1044.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;927&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;PinpointKit in action&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Starting today with our &lt;a href=&quot;https://github.com/Lickability/PinpointKit/releases/tag/0.9&quot;&gt;preview release&lt;/a&gt;, if you make an iOS app and want to collect annotated feedback from your testers or users, you can include the PinpointKit framework via &lt;a href=&quot;https://cocoapods.org&quot;&gt;Cocoapods&lt;/a&gt; or &lt;a href=&quot;https://github.com/Carthage/Carthage&quot;&gt;Carthage&lt;/a&gt;. Whenever the user shakes their device in your app, they’ll be presented with a fully customizable feedback sheet that can send annotated screenshots and feedback via email or straight to your bug tracker. They can add arrows, boxes and text, blur out sensitive information, and even include a full console log.&lt;/p&gt;&lt;p&gt;Everything in PinpointKit is customizable. Flows, fonts, colors, and even behavior can all be controlled and overridden to fit your specific needs and feel at home in your app.&lt;/p&gt;&lt;p&gt;This is our first open-source release, so be sure to reach out on &lt;a href=&quot;https://twitter.com/lickability&quot;&gt;Twitter&lt;/a&gt; or &lt;a href=&quot;https://github.com/lickability/PinpointKit&quot;&gt;GitHub&lt;/a&gt; with improvements that we can make before calling PinpointKit a 1.0. And if you’d like to support the continued development of PinpointKit, buy a color or two via in-app purchase in Pinpoint or hire us to build or improve your app.&lt;/p&gt;&lt;p&gt;PinpointKit is inspired by &lt;a href=&quot;https://github.com/marcoarment/BugshotKit&quot;&gt;BugshotKit&lt;/a&gt; from Marco Arment. Huge thanks to &lt;a href=&quot;https://twitter.com/marcoarment&quot;&gt;Marco&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/calebd&quot;&gt;Caleb&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/pearapps&quot;&gt;Kenny&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/paulrehkugler&quot;&gt;Paul&lt;/a&gt;, and everyone here at Lickability that worked on PinpointKit.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/lickability/PinpointKit&quot;&gt;Try it out&lt;/a&gt; in your apps and let us know what you think!&lt;/p&gt; </content:encoded><author>mb bischoff</author></item><item><title>Michael Liberatore Joins Lickability</title><link>https://lickability.com/blog/michael-liberatore-joins-lickability/</link><guid isPermaLink="true">https://lickability.com/blog/michael-liberatore-joins-lickability/</guid><description>Michael Liberatore Joins Lickability</description><pubDate>Sun, 08 May 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Michael is the first full-time developer to join our team at &lt;a href=&quot;https://lickability.com&quot;&gt;Lickability&lt;/a&gt;. He started today as an iOS Engineer in our &lt;a href=&quot;https://twitter.com/lickability/status/727222034103713792&quot;&gt;brand-new New York office&lt;/a&gt;. Just like &lt;a href=&quot;https://twitter.com/mb&quot;&gt;mb&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/bcapps&quot;&gt;Brian&lt;/a&gt;, and &lt;a href=&quot;https://twitter.com/twig777&quot;&gt;Andrew&lt;/a&gt;, he’ll be working with our fantastic clients and improving our &lt;a href=&quot;https://acceleratorapp.com&quot;&gt;existing&lt;/a&gt;&lt;a href=&quot;https://lickability.com/pinpoint&quot;&gt;products&lt;/a&gt; for customers.&lt;/p&gt;&lt;p&gt;Welcome Michael, it’s great to have you on the team! Let’s find out a little more about him…&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/468702bb3de44883de3fd4f33450d789b126d351-2000x1500.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=400 400w, https://cdn.sanity.io/images/nkt6o869/production/468702bb3de44883de3fd4f33450d789b126d351-2000x1500.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=800 800w, https://cdn.sanity.io/images/nkt6o869/production/468702bb3de44883de3fd4f33450d789b126d351-2000x1500.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1200 1200w, https://cdn.sanity.io/images/nkt6o869/production/468702bb3de44883de3fd4f33450d789b126d351-2000x1500.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600 1600w, https://cdn.sanity.io/images/nkt6o869/production/468702bb3de44883de3fd4f33450d789b126d351-2000x1500.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=2000 2000w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/468702bb3de44883de3fd4f33450d789b126d351-2000x1500.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1600&quot; width=&quot;1600&quot; height=&quot;1200&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Michael Liberatore on his first day in the office&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;h3&gt;How’d you get into building apps for iOS?&lt;/h3&gt;&lt;p&gt;I’ve been a Mac user since &lt;a href=&quot;https://%28https://en.wikipedia.org/wiki/System_7&quot;&gt;System 7&lt;/a&gt;. I began coding much later, but eventually taught myself Objective-C to write my own Mac apps. In college, I remember waiting patiently after requesting to join the developer program when the first iPhone SDK was announced.&lt;/p&gt;&lt;p&gt;I couldn’t wait to run Apple’s OpenGL ES sample projects on my phone. I spent most of my time toying around with 3D graphics and I fell in love with iPhone OS development the first time I used the accelerometer to control the position of a 3D model. It blew my mind how easy it was to set up. I knew right away that I eventually wanted to make a career out of writing software for such an amazing platform.&lt;/p&gt;&lt;h3&gt;What did you do before joining Lickability?&lt;/h3&gt;&lt;p&gt;After college, I worked for five years primarily as a Mac Developer writing education and interactive participation software for a company in Ohio called &lt;a href=&quot;https://www.turningtechnologies.com&quot;&gt;Turning Technologies&lt;/a&gt;. Turning had a few iOS apps to which I hadn’t contributed much, but my final project was to lead a rewrite of one of them. In 2014, I moved from Ohio to New York to work for the iOS team at &lt;em&gt;The New York Times.&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;What apps and developers inspire you to make your best work?&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://panic.com&quot;&gt;Panic&lt;/a&gt; was a huge inspiration of mine when I first started Mac development, and continues to be today. For iOS, my most used apps are &lt;a href=&quot;https://tapbots.com/tweetbot/&quot;&gt;Tweetbot&lt;/a&gt; by &lt;a href=&quot;https://tapbots.com&quot;&gt;Tapbots&lt;/a&gt; and &lt;a href=&quot;https://overcast.fm&quot;&gt;Overcast&lt;/a&gt; by &lt;a href=&quot;https://marco.org/&quot;&gt;Marco Arment&lt;/a&gt;, which constantly inspire me to match their obsessive detail.&lt;/p&gt;&lt;h3&gt;Tell us a little more about what you’re excited to work on here.&lt;/h3&gt;&lt;p&gt;Like the companies and apps I mentioned before, Lickability has the reputation for having obsessive attention to detail. I’m excited to match that obsession with any and all projects. I’m also a huge fan of &lt;a href=&quot;https://lickability.com/pinpoint/&quot;&gt;Pinpoint&lt;/a&gt;, so I look forward to making contributions there.&lt;/p&gt;&lt;h3&gt;Favorite ice cream flavor, and why?&lt;/h3&gt;&lt;figure class=&quot;small&quot; data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/d86046a07c7465797f54191d2e7e6cb3d889c687-250x230.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=125 125w, https://cdn.sanity.io/images/nkt6o869/production/d86046a07c7465797f54191d2e7e6cb3d889c687-250x230.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=250 250w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/d86046a07c7465797f54191d2e7e6cb3d889c687-250x230.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=250&quot; width=&quot;250&quot; height=&quot;230&quot;/&gt; &lt;figcaption data-astro-cid-c6ccksbc&gt; &lt;p&gt;Mike’s favorite, served the &lt;a href=&quot;https://www.grubstreet.com/2015/12/dairy-queen-blizzard-upside-down&quot;&gt;proper way&lt;/a&gt;&lt;/p&gt;  &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Not exactly a flavor (or really ice cream for that matter), but I have to go with &lt;a href=&quot;https://www.dairyqueen.com/us-en/Menu/Treats/Blizzard-Treats/Reeses-Peanut-Butter-Cup/&quot;&gt;Reese’s Peanut Butter Cup Blizzards&lt;/a&gt; from Dairy Queen. My sister Amanda worked at Dairy Queen when I was growing up, so our family had Dairy Queen quite a bit, and I’ve tried them all.&lt;/p&gt;&lt;h3&gt;Anything else you’d like to tell the folks at home?&lt;/h3&gt;&lt;p&gt;Just to thank my friends and family back home in Ohio for their support. Their encouragement led me to New York, which ultimately led me here.&lt;/p&gt;&lt;p&gt;I’m tremendously excited to join Lickability and start coding!&lt;/p&gt;&lt;p&gt;We’re absolutely thrilled to have Michael joining us. And we can’t wait for you to see what we build together. If you see him &lt;a href=&quot;https://twitter.com/mliberatore&quot;&gt;on Twitter&lt;/a&gt; or in real life, give him a high five for us. 🙏&lt;/p&gt;&lt;p&gt;And as always, if you’re in the market for a group of talented mobile engineers to build your app, train your team, or provide some recommendations, don’t hesitate to reach out.&lt;/p&gt; </content:encoded><author>mb bischoff</author></item><item><title>Helping Build The New Yorker Today</title><link>https://lickability.com/blog/helping-build-the-new-yorker-today/</link><guid isPermaLink="true">https://lickability.com/blog/helping-build-the-new-yorker-today/</guid><description>Helping Build The New Yorker Today</description><pubDate>Sun, 17 Apr 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When &lt;em&gt;The New Yorker&lt;/em&gt; first launched its &lt;a href=&quot;http://www.newyorker.com/news/news-desk/jason-schwartzman-introduces-the-new-yorker-ipad-app&quot;&gt;flagship iPad app&lt;/a&gt; in 2010 and an &lt;a href=&quot;http://www.newyorker.com/culture/culture-desk/introducing-the-new-yorkers-iphone-edition&quot;&gt;iPhone version&lt;/a&gt; two years later, it was designed to mimic the magazine’s typographic layout. But as phones have become a bigger part of our lives, many subscribers want to read in a super-fast, native experience that’s not based around individual issues, but rather updates every single day. And now they can with &lt;a href=&quot;http://www.newyorker.com/today&quot;&gt;&lt;strong&gt;The New Yorker Today&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/1766fcb3f1c9cef3d51d1ba1ef54acc66265574f-1024x768.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=256 256w, https://cdn.sanity.io/images/nkt6o869/production/1766fcb3f1c9cef3d51d1ba1ef54acc66265574f-1024x768.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=512 512w, https://cdn.sanity.io/images/nkt6o869/production/1766fcb3f1c9cef3d51d1ba1ef54acc66265574f-1024x768.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=768 768w, https://cdn.sanity.io/images/nkt6o869/production/1766fcb3f1c9cef3d51d1ba1ef54acc66265574f-1024x768.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1024 1024w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/1766fcb3f1c9cef3d51d1ba1ef54acc66265574f-1024x768.png?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=1024&quot; width=&quot;1024&quot; height=&quot;768&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;We worked very closely with Condé Nast’s team of incredibly talented designers, editors, engineers, and project managers to build an app that we’re all really proud of. And just days after it was released, Apple featured it on the front page of the App Store as one of the &lt;em&gt;Best New Apps.&lt;/em&gt;&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/c422cb1641737d5fa8ab3e7ef4d39c57368f6176-750x421.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=188 188w, https://cdn.sanity.io/images/nkt6o869/production/c422cb1641737d5fa8ab3e7ef4d39c57368f6176-750x421.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=375 375w, https://cdn.sanity.io/images/nkt6o869/production/c422cb1641737d5fa8ab3e7ef4d39c57368f6176-750x421.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=563 563w, https://cdn.sanity.io/images/nkt6o869/production/c422cb1641737d5fa8ab3e7ef4d39c57368f6176-750x421.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=750 750w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/c422cb1641737d5fa8ab3e7ef4d39c57368f6176-750x421.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=750&quot; width=&quot;750&quot; height=&quot;421&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;The app was engineered entirely in Swift for iOS 9 and above, with a focus on &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2015/408/&quot;&gt;protocol-oriented design&lt;/a&gt;, reusable components, speed, and &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2015/218/&quot;&gt;modern layout systems&lt;/a&gt; that will allow the app to easily adapt if it ever comes to other devices or platforms.&lt;/p&gt;&lt;p&gt;Our friend and frequent collaborator, &lt;a href=&quot;https://twitter.com/khanlou&quot;&gt;Soroush Khanlou&lt;/a&gt;, who writes about development at &lt;a href=&quot;http://khanlou.com&quot;&gt;khanlou.com&lt;/a&gt;, spearheaded much of the engineering effort alongside &lt;em&gt;The New Yorker’s&lt;/em&gt; very own &lt;a href=&quot;https://jsplash.carbonmade.com/about&quot;&gt;Justin Savory&lt;/a&gt; who built the app’s super fun and seemingly never-ending, swipeable cartoon gallery.&lt;/p&gt;&lt;p&gt;Whether you’re a diehard fan of the magazine or you just like reading the captions on the cartoons, we’d love for you to &lt;a href=&quot;https://geo.itunes.apple.com/us/app/the-new-yorker-today/id1081530898?at=10l4Vh&amp;ct=medium&quot;&gt;&lt;strong&gt;download the app&lt;/strong&gt;&lt;/a&gt;, try it out, and let us know what you think. For the first month, everything in there is completely free, and after that you can subscribe with a quick in-app purchase.&lt;/p&gt;&lt;p&gt;And if you or your team need help building a new app or updating an existing one, we’d love to hear about it. You can email us at &lt;strong&gt;hello@lickability.com&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;&lt;em&gt;“The New Yorker” and “Condé Nast” are registered trademarks of Condé Nast. “App Store” is a service mark of Apple Inc.&lt;/em&gt;&lt;/p&gt; </content:encoded><author>mb bischoff</author></item><item><title>Going Indie</title><link>https://lickability.com/blog/going-indie/</link><guid isPermaLink="true">https://lickability.com/blog/going-indie/</guid><description>Going Indie</description><pubDate>Mon, 06 Apr 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;On October 13, 2013 a plan was hatched. It wasn’t a diabolical &lt;em&gt;House of Cards&lt;/em&gt; political gambit or even anything more concrete than a rough outline. No, on the seven hour drive back from &lt;a href=&quot;https://cingleton.com&quot;&gt;Çingleton&lt;/a&gt; in Montreal, we planned to take our small, part-time app development company, Lickability, full time in 2015. And today, right on schedule, I am starting as the first full-time member of Lickability.&lt;/p&gt;&lt;p&gt;In the summer of 2009, right around high school graduation, my friend &lt;a href=&quot;https://mbbischoff.com/&quot;&gt;mb Bischoff&lt;/a&gt; called me up one night with their idea for this new thing called an “app.” Armed with nothing but an idea and an abundance of teenage hubris, we built our first iOS app Broadway for theater showtimes, and a partnership was born. We officially created &lt;a href=&quot;https://lickability.com&quot;&gt;Lickability&lt;/a&gt; that year on our laptops at a local Panera, and two new apps and countless hours later, we’re making that company dreamed up by two kids six years ago into a small, world-class software studio.&lt;/p&gt;&lt;p&gt;With a full-time engineer, we will be giving some much-needed love to our existing apps &lt;a href=&quot;https://acceleratorapp.com&quot;&gt;Velocity&lt;/a&gt; and &lt;a href=&quot;https://quotebookapp.com&quot;&gt;Quotebook&lt;/a&gt; this year, adding a few nifty iOS 8 features and overdue bug fixes. Additionally, while Lickability has done some selective consulting in the past, we’ll now be taking on more client work, and we’re excited to help others build &lt;a href=&quot;https://blog.lickability.com/steve-jobs-on-aqua-6c0e6f24a1de#&quot;&gt;lickable&lt;/a&gt; apps. We’ve got a team of three talented people who have worked for The New York Times and Tumblr and collectively have over 15 years of experience building iOS apps. If you or anyone you know would like to discuss working on an app, feel free to say &lt;a href=&quot;mailto:hello@lickability.com&quot;&gt;hello&lt;/a&gt; — we love talking to people about their ideas.&lt;/p&gt;&lt;p&gt;One thing that we’ve always done at Lickability is take our time and do things the “right way.” We take our time to ship releases because we want them to be up to our standards and make sure that they’ve got the features and polish that they deserve. We’re taking the same approach with our company. For the past two years we’ve worked on app releases and client projects that have allowed us to save enough money to pay me full time for more than half a year. Without taking any external investment, we’re funding this experiment entirely from the proceeds generated so far by Lickability. While the initial outside funding approach has worked for many, we’re proud to be responsible to no one but ourselves for our future.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Six months&lt;/strong&gt;. That’s how long we’ve given ourselves to make this work. Like with any business, it’s important to determine how to become profitable over a period of time. So, paying me a full-time salary, we’re going to be profitable in six months or I’m going to start looking for a new gig. As much as we’re idealists who would love to toil away on perfecting products, we understand the realities of starting a small business in a volatile industry. I’m confident that we’re going to meet and exceed our goal, but it’s necessary to have a realistic and well-defined metric for success, and profitability within six months is ours.&lt;/p&gt;&lt;p&gt;Everyone I’ve talked to about this plan has a laundry list of questions: Are you taking funding? Will you have an office? Who’s going full time next? What does success look like? Who’s this phantom third &lt;a href=&quot;https://twitter.com/Twig777&quot;&gt;member&lt;/a&gt; of Lickability? We’ve made some incredible friends who have successfully started their own businesses — &lt;a href=&quot;https://www.marco.org&quot;&gt;Marco Arment&lt;/a&gt;, &lt;a href=&quot;https://www.allenpike.com&quot;&gt;Allen Pike&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/imyke&quot;&gt;Myke Hurley&lt;/a&gt;, &lt;a href=&quot;https://one37.net&quot;&gt;Matt Alexander&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/markkawano&quot;&gt;Mark Kawano&lt;/a&gt;, to name a few — who we have relied on for advice and answers to those same questions. But not everyone has that same luxury, so I’ll be writing about what it’s like to build an app development company on the &lt;a href=&quot;https://blog.lickability.com&quot;&gt;Lickability blog&lt;/a&gt; whenever possible. Hopefully I can provide a peek behind the curtain and insight into some of the fears, challenges, and triumphs of growing our company.&lt;/p&gt;&lt;blockquote&gt;“We made the buttons on the screen look so good you’ll want to lick them.” &lt;em&gt;– Steve Jobs&lt;/em&gt;&lt;/blockquote&gt;&lt;p&gt;When I &lt;a href=&quot;https://twitter.com/bcapps/status/581532065574727680&quot;&gt;announced&lt;/a&gt; that I was leaving The New York Times a little over a week ago, the outpouring of support before I had even mentioned what was next was beyond imagination. We would not be here without that same support from all of the amazing people who have helped us over the years, and for that we are sincerely grateful. I’m excited for the next chapter, and I know that we will make Lickability into something great. But for now, it’s time for me to get back to work.&lt;/p&gt; </content:encoded><author>Brian Capps</author></item><item><title>Quotebook 3: The Reviews Are In</title><link>https://lickability.com/blog/quotebook-3-the-reviews-are-in/</link><guid isPermaLink="true">https://lickability.com/blog/quotebook-3-the-reviews-are-in/</guid><description>Quotebook 3: The Reviews Are In</description><pubDate>Fri, 29 Aug 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Earlier this week, we released the third major version of &lt;a href=&quot;http://quotebookapp.com&quot;&gt;Quotebook&lt;/a&gt;. Here’s what the press had to say:&lt;/p&gt;&lt;figure data-astro-cid-c6ccksbc&gt; &lt;img alt=&quot;&quot; loading=&quot;lazy&quot; data-astro-cid-c6ccksbc=&quot;true&quot; srcSet=&quot;https://cdn.sanity.io/images/nkt6o869/production/98392da8bdb17fc08fb64204efbc967e7c0488a3-500x375.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=250 250w, https://cdn.sanity.io/images/nkt6o869/production/98392da8bdb17fc08fb64204efbc967e7c0488a3-500x375.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=500 500w&quot; src=&quot;https://cdn.sanity.io/images/nkt6o869/production/98392da8bdb17fc08fb64204efbc967e7c0488a3-500x375.jpg?auto=format&amp;amp;fit=max&amp;amp;q=75&amp;amp;w=500&quot; width=&quot;500&quot; height=&quot;375&quot;/&gt;  &lt;/figure&gt; &lt;p&gt;&lt;a href=&quot;http://www.imore.com/quotebook-digital-book-all-your-favorite-quotes&quot;&gt;&lt;strong&gt;iMore:&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;blockquote&gt;We all have our favorite quotes, and the newly overhauled Quotebook app aims to help us store and organize them all. The app, with the kind of clean design we expect from Lickability, makes it easy to keep all of your favorite quotes in one place, with ratings, sources, tagging, and more.&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;http://www.macstories.net/reviews/quotebook-3-0-brings-new-design-revamped-management-of-authors-and-sources/&quot;&gt;&lt;strong&gt;MacStories:&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;a href=&quot;http://quotebookapp.com/&quot;&gt;Quotebook&lt;/a&gt;, developed by the folks at &lt;a href=&quot;http://lickability.com/&quot;&gt;Lickability&lt;/a&gt;, has long been my favorite app to save and archive quotes and passages on the iPhone and iPad.&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;http://minimalmac.com/post/95927321024/great-ways-to-use-quotebook&quot;&gt;&lt;strong&gt;Minimal Mac:&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;blockquote&gt;Hopefully, this will help you see why I love the app so much and it has been on my home screen since the day it was released. The new version is certainly worth your time checking out.&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;http://beautifulpixels.com/iphone/quotebook-3-ios/&quot;&gt;&lt;strong&gt;Beautiful Pixels:&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;blockquote&gt;I can’t believe the update is free because of how much has been added, let alone the brand new interface and the fact that it is universal. Quotebook is available on the App Store for $4.99 and is a free update for existing owners.&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;http://toolsandtoys.net/quotebook-3-for-ios/&quot;&gt;&lt;strong&gt;Tools and Toys:&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;blockquote&gt;With today’s update, this universal app is better than ever. You can now add images and descriptions to your authors and sources, import quotes from Tumblr and Facebook, and share your quotes to apps like Tweetbot and Day One.&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;http://www.512pixels.net/blog/2014/8/quotebook-3&quot;&gt;&lt;strong&gt;512 Pixels:&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;blockquote&gt;Quotebook 3 by the handsome young men at Likability is not messy or boring. Some of the best apps on iOS are ones that do very specific things. Quotebook is in that class.&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;http://petedenison.net/2014/08/30/quotebook-3-for-ios/&quot;&gt;&lt;strong&gt;Pete Denison:&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;blockquote&gt;Developers say many things in press releases, however on this point from Lickability I most certainly concur:&lt;br&gt;&lt;br&gt;&lt;em&gt;Lickability has been and will always be concerned with all the small details that make apps great.&lt;/em&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;http://techese.net/articles/2014/8/quotebook-3&quot;&gt;&lt;strong&gt;techēse:&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;blockquote&gt;Quotebook is simply a delightful app that I have used for years to collect bits of wisdom from notable people, friends, and even my son. It’s not only perfect for quickly recording a great quote, but it makes returning to those quotes and reflecting on them frictionless.&lt;/blockquote&gt; </content:encoded><author>mb bischoff</author></item><item><title>Quotebook 3: Coming Soon</title><link>https://lickability.com/blog/quotebook-3-coming-soon/</link><guid isPermaLink="true">https://lickability.com/blog/quotebook-3-coming-soon/</guid><description>Quotebook 3: Coming Soon</description><pubDate>Wed, 26 Mar 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you’ve been paying attention on &lt;a href=&quot;https://twitter.com/quotebookapp&quot;&gt;Twitter&lt;/a&gt;, you may know that we’ve been hard at work on a brand new update to Quotebook. Today we’re announcing that the third major version of Quotebook is coming soon to an iPhone or iPad near you.&lt;/p&gt;&lt;h3&gt;What’s New&lt;/h3&gt;&lt;p&gt;Quotebook 3 is an all new version of Quotebook. Quotebook lets you build a personal library of quotes and organize and share those quotes with your friends on social networks. And now, we’ve rewritten every single line of code, we’ve redesigned every single screen for iOS 7, and we’ve added dozens of the features you’ve been requesting.&lt;/p&gt;&lt;p&gt;It’s the most ambitious update we’ve ever done and it’s almost ready.&lt;/p&gt;&lt;h3&gt;Timeline&lt;/h3&gt;&lt;p&gt;Quotebook 3 for iPhone is currently in private alpha. After we’re comfortable with our final design on iPhone, we’ll finalize our iPad design and begin a private beta with a few more users.&lt;/p&gt;&lt;p&gt;After that, it’s a month or so of testing until we submit to the App Store. We can’t give you a firm date just yet, but Quotebook 3 is our top priority, and we know you’re eagerly awaiting the update as much as we are.&lt;/p&gt;&lt;p&gt;You can &lt;a href=&quot;https://quotebookapp.com&quot;&gt;sign up to be notified&lt;/a&gt; when Quotebook 3 is released at &lt;a href=&quot;https://quotebookapp.com&quot;&gt;our website&lt;/a&gt; or &lt;a href=&quot;https://twitter.com/quotebookapp&quot;&gt;follow us on Twitter&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Until Then…&lt;/h3&gt;&lt;p&gt;Quotebook 3 will be a free update for all existing Quotebook users and it’ll download to your devices automatically if you already have Quotebook installed. All your quotes will still be there, and they’ll look even better.&lt;/p&gt;&lt;p&gt;In the meantime, we’ve pulled Quotebook 2 from the store because it was starting to feel outdated on iOS 7. We didn’t want any new customers downloading it and being disappointed. Quotebook will return to the store as soon as 3.0 is released.&lt;/p&gt;&lt;p&gt;If you need support for Quotebook 2, we’re always happy to help at &lt;strong&gt;support@quotebookapp.com&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;Thanks for your continued support and patience. Quotebook is a big app and it’s taken us longer than we expected to rewrite, but it’ll be worth the wait. Time to get back to work on finishing the best release of Quotebook yet.&lt;/p&gt; </content:encoded><author>mb bischoff</author></item><item><title>Velocity URL Scheme</title><link>https://lickability.com/blog/velocity-url-scheme/</link><guid isPermaLink="true">https://lickability.com/blog/velocity-url-scheme/</guid><description>Velocity URL Scheme</description><pubDate>Sun, 06 Oct 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Our new speed reading app, &lt;a href=&quot;https://velocityapp.com&quot;&gt;Velocity&lt;/a&gt;, has a couple of awesome features for advanced users that we’ll highlight over the coming days. One of them is the app’s URL scheme. A URL scheme is a way to launch and perform actions in an app simply by typing in a URL or clicking a link in a browser. It’s also how apps on your device communicate with one another. If you’re interested in playing with it, you can already use the URL scheme from &lt;a href=&quot;https://contrast.co/launch-center-pro/&quot;&gt;Launch Center Pro&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;If you’re a developer, we’d love it if you added “Read in Velocity” support in your app, and we’d be happy to help make that easier. Email us at &lt;a href=&quot;mailto:hello@velocityapp.com&quot;&gt;hello@velocityapp.com&lt;/a&gt; if you have any questions, comments, or complaints. And feel free to let us know if you would like us to add more features to the URL scheme.&lt;/p&gt;&lt;p&gt;Here’s how the URL scheme for Velocity works:&lt;/p&gt;&lt;h3&gt;General&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;velocity://&lt;/li&gt;&lt;li&gt;Opens the app&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Reading&lt;/h3&gt;&lt;p&gt;These URLs will immediately read the text or the content of the URL provided.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;//read?text=Text%20goes%20here&quot;&gt;velocity://read?text=Text%20goes%20here&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Reads the text: “Text goes here” in Velocity.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;//movies.nytimes.com/2013/10/04/movies/gravity-stars-sandra-bullock-and-george-clooney.html&quot;&gt;velocity://read?url=http://movies.nytimes.com/2013/10/04/movies/gravity-stars-sandra-bullock-and-george-clooney.html&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Reads the text at the given URL in Velocity.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Saving&lt;/h3&gt;&lt;p&gt;These URLs will save the text or the content of the URL provided for later reading. They will be saved into the user’s local source (e.g. iPhone).&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;//save?text=Text%20goes%20here&quot;&gt;velocity://save?text=Text%20goes%20here&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Saves the text: “Text goes here” to the local source in Velocity.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;//movies.nytimes.com/2013/10/04/movies/gravity-stars-sandra-bullock-and-george-clooney.html&quot;&gt;velocity://save?url=http://movies.nytimes.com/2013/10/04/movies/gravity-stars-sandra-bullock-and-george-clooney.html&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Saves the text at the given URL to the local source in Velocity.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Editing&lt;/h3&gt;&lt;p&gt;These URLs will save the text or the content of the URL provided for later reading and then allow the user to immediately edit the text. They will be saved into the user’s local source (e.g. iPhone).&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;//edit?text=Text%20goes%20here&quot;&gt;velocity://edit?text=Text%20goes%20here&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Saves the text: “Text goes here” to the local source in Velocity and then displays an editing view.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;//movies.nytimes.com/2013/10/04/movies/gravity-stars-sandra-bullock-and-george-clooney.html&quot;&gt;velocity://edit?url=http://movies.nytimes.com/2013/10/04/movies/gravity-stars-sandra-bullock-and-george-clooney.html&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Saves the text at the given URL to the local source in Velocity and then displays an editing view.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;If you’re a developer, we look forward to seeing some of the awesome stuff you make with our URL scheme. If not, is there an app you’d like to see Velocity support?&lt;/p&gt; </content:encoded><author>mb bischoff</author></item><item><title>“Why Doesn’t Quotebook Use Dropbox?”</title><link>https://lickability.com/blog/why-doesn-t-quotebook-use-dropbox/</link><guid isPermaLink="true">https://lickability.com/blog/why-doesn-t-quotebook-use-dropbox/</guid><description>“Why Doesn’t Quotebook Use Dropbox?”</description><pubDate>Sun, 30 Jun 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few of our most-asked syncing related support questions are:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Why is Quotebook different than other iCloud apps?&lt;/li&gt;&lt;li&gt;What are we doing to fix syncing?&lt;/li&gt;&lt;li&gt;Why not Dropbox?&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;In today’s blog post, we’d like to shed a little light on these topics. Later we’ll get into the nuts and bolts of how Quotebook syncing works, but for now we’re going to focus on these three queries. Let’s divide them up to keep it simple.&lt;/p&gt;&lt;h3&gt;Why is Quotebook different?&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;The simple answer is that Quotebook uses a different system than other apps.&lt;/strong&gt; The long answer is that iCloud apps work using one of three technologies:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Key-Value Data&lt;/li&gt;&lt;li&gt;Documents in iCloud&lt;/li&gt;&lt;li&gt;Core Data&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;We don’t use Key-Value syncing because it’s designed for limited amounts of data that doesn’t need to change very often. It’s great for things like Settings or User Preferences, but it doesn’t work for Quotebook — and it has no conflict resolution.&lt;/p&gt;&lt;p&gt;Documents in iCloud work in a similar way to Dropbox. It also isn’t the right fit for Quotebook because it’s designed for something like Microsoft Word, wherein you open one of multiple documents, work on it, and close it before continuing on with another. Many apps our users use in addition to Quotebook operate using this method, and while it’s a good one, it doesn’t handle databases.&lt;/p&gt;&lt;p&gt;Quotebook uses Core Data. It stores information as a database (which we need) and has automatic conflict resolution. This works perfectly for us because our app uses a ton of information often spread across multiple devices.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;For more information on Key-Value Data, &lt;a href=&quot;https://developer.apple.com/library/mac/#documentation/General/Conceptual/iCloudDesignGuide/Chapters/DesigningForKey-ValueDataIniCloud.html&quot;&gt;read the article here&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;For more about Core Data, &lt;a href=&quot;https://developer.apple.com/library/mac/#documentation/General/Conceptual/iCloudDesignGuide/Chapters/DesignForCoreDataIniCloud.html#//apple_ref/doc/uid/TP40012094-CH3-SW1&quot;&gt;read here&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;For more on Documents in iCloud, &lt;a href=&quot;https://developer.apple.com/library/mac/#documentation/General/Conceptual/iCloudDesignGuide/Chapters/DesignForCoreDataIniCloud.html%23//apple_ref/doc/uid/TP40012094-CH3-SW1&quot;&gt;read here&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;What are we doing to fix syncing?&lt;/h3&gt;&lt;p&gt;When we spoke to Apple at the &lt;a href=&quot;https://developer.apple.com/wwdc/&quot;&gt;World Wide Developers Conference&lt;/a&gt;, they told us that they would have many of our syncing problems fixed in iOS7. That comes out in the fall, and if all goes well it’ll fix a lot of the issues people have been having loading their quotes from iCloud.&lt;/p&gt;&lt;p&gt;A couple of things that will improve the syncing issues some of you have been having are that they’ve given us better debugging tools — Gauges are a good example. Gauges will let us see how much usage is in an iCloud account and what is uploaded/downloaded so that we can see the problems in development rather than guessing at them. It’s proactive instead of reactive debugging, and it should make everything move much more smoothly.&lt;/p&gt;&lt;p&gt;Luckily iOS7 is only a few months away, but while we wait for these new tools all we can ask for is your patience. In the meantime, you can &lt;a href=&quot;https://www.apple.com/apple-events/june-2013/&quot;&gt;watch the keynote from WWDC here.&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;Why not Dropbox?&lt;/h3&gt;&lt;p&gt;We understand that Dropbox is really useful for a lot of our users, and we think it’s a great piece of technology. Unfortunately, it’s set up in a way that doesn’t work well with Quotebook.&lt;/p&gt;&lt;p&gt;The main issue with Dropbox is that it doesn’t handle databases that need concurrent access — like Quotebook, for example. Concurrent access is essentially multiple devices writing and reading data at the same time, like if you had Quotebook on your iPhone as well as an iPad.&lt;/p&gt;&lt;p&gt;With Dropbox, the most recent update to your database of quotes will overwrite any others. For example, if you had both your iPhone and your iPad offline and added quotes to each, then only the quotes from the latest device you updated will show up when you reconnect both devices. That could mean people losing quotes forever, and that’s not something we want to happen.&lt;/p&gt;&lt;p&gt;By using iCloud and Core Data, we make sure that all of your changes from any devices are logged and marked at the same time. It’s a lot more efficient, and it keeps anyone from losing data. While syncing might currently be taking a while, as mentioned we hope to have that fixed soon.&lt;/p&gt;&lt;p&gt;Thank you for your patience with us, and we hope these explanations help you better understand the inner workings of Quotebook!&lt;/p&gt;&lt;p&gt;If you’ve got any more questions for us, feel free to get in touch:&lt;/p&gt;&lt;p&gt;Twitter: &lt;a href=&quot;https://twitter.com/quotebookapp&quot;&gt;@quotebookapp&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Email: &lt;a href=&quot;mailto:support@quotebookapp.com&quot;&gt;support@quotebookapp.com&lt;/a&gt;&lt;/p&gt; </content:encoded><author>mb bischoff</author></item><item><title>Quotebook 2.0 API and URL Scheme</title><link>https://lickability.com/blog/quotebook-2-0-api-and-url-scheme/</link><guid isPermaLink="true">https://lickability.com/blog/quotebook-2-0-api-and-url-scheme/</guid><description>Quotebook 2.0 API and URL Scheme</description><pubDate>Fri, 12 Oct 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You may already know that in &lt;a href=&quot;https://quotebookapp.com&quot;&gt;Quotebook&lt;/a&gt; 2.0, we added the ability to launch Quotebook from apps like &lt;a href=&quot;https://instapaper.com&quot;&gt;Instapaper&lt;/a&gt;, &lt;a href=&quot;https://draftsapp.com&quot;&gt;Drafts&lt;/a&gt;, and &lt;a href=&quot;https://appcubby.com/launch-center/&quot;&gt;Launch Center Pro&lt;/a&gt;. The way that works is through a “custom URL scheme” which just means that we register to handle any links that start with quotebook://.&lt;/p&gt;&lt;p&gt;Here’s the kinds of links we handle and how they work. We’d love it if you added “Add to Quotebook” support in your app, and we’d love to help make that easier. Email us at hello@quotebookapp.com if you have any questions, comments, or complaints.&lt;/p&gt;&lt;h3&gt;General Navigation&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;quotebook:// — Opens the app&lt;/li&gt;&lt;li&gt;quotebook://quotes — Goes to the Quotes Tab&lt;/li&gt;&lt;li&gt;quotebook://authors — Goes to the Authors Tab&lt;/li&gt;&lt;li&gt;quotebook://sources — Goes to the Sources Tab&lt;/li&gt;&lt;li&gt;quotebook://tags — Goes to the Tags tab&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Adding Quotes&lt;/h3&gt;&lt;figure class=&quot;not-prose&quot; data-code-block data-astro-cid-vjd3lxvn&gt; &lt;figcaption class=&quot;text-fl-sm font-light flex justify-between items-center&quot; data-astro-cid-vjd3lxvn&gt; &lt;span data-astro-cid-vjd3lxvn&gt;text&lt;/span&gt; &lt;button class=&quot;copy-button border-none font-bold&quot; disabled title=&quot;Requires JavaScript&quot; data-astro-cid-vjd3lxvn&gt; &lt;span class=&quot;inline-block&quot; data-astro-cid-vjd3lxvn&gt;Copy&lt;/span&gt; &lt;/button&gt; &lt;/figcaption&gt; &lt;div class=&quot;relative&quot; data-astro-cid-vjd3lxvn&gt; &lt;pre class=&quot;shiki dark-plus p-fl-xs rounded-lg overflow-x-auto text-fl-sm&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;quotebook://add?quote=Stay%20Hungry.%20Stay%20Foolish.&amp;#x26;author=Steve%20Jobs&amp;#x26;source=Whole%20Earth%20Catalog&amp;#x26;rating=5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; &lt;/div&gt; &lt;/figure&gt;  &lt;ul&gt;&lt;li&gt;Adds a quote “Stay Hungry. Stay foolish.” to the user’s Quotebook with the author Steve Jobs, the source Whole Earth Catalog with a rating of 5 stars.&lt;/li&gt;&lt;li&gt;All of these parameters are optional except for quote which is required. Any unused or blank parameter need not be included.&lt;/li&gt;&lt;li&gt;The rating must be between 0 and 5.&lt;/li&gt;&lt;li&gt;The author and source may be names, twitter usernames prefixed by an @, or URLs.&lt;/li&gt;&lt;/ul&gt; </content:encoded><author>mb bischoff</author></item></channel></rss>