<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <link href="https://www.jessesquires.com/feed.xml" rel="self" type="application/atom+xml" />
    <generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator>

    <id>https://www.jessesquires.com/</id>
    <link href="https://www.jessesquires.com/" />
    <updated>2026-03-01T22:42:10-08:00</updated>

    <title>Jesse Squires</title>
    <subtitle>Turing complete with a stack of 0xdeadbeef</subtitle>
    <icon>https://www.jessesquires.com/img/logo.png</icon>
    <rights>Copyright © 2012-2026, Jesse Squires</rights>
    <author>
        <name>Jesse Squires</name>
        <uri>https://www.jessesquires.com/</uri>
    </author>

    

    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2025/11/07/mariposa/</id>
        <link href="https://www.jessesquires.com/blog/2025/11/07/mariposa/" />
        <title>Mariposa: a lightweight Swift CLI to automate sharing blog posts to social media</title>
        <published>2025-11-07T07:58:20-08:00</published>
        <updated>2025-11-07T07:58:20-08:00</updated>

        <category term="software-dev" />
        <category term="social-media" /><category term="website-infra" /><category term="swift" />
        <summary type="html">&lt;p&gt;After writing and publishing a new post, I always share to social media because that is how a lot of folks receive updates (also, &lt;a href=&quot;https://indieweb.org/POSSE&quot;&gt;POSSE&lt;/a&gt;). Because I hate using social media, I have automated this process with a lightweight CLI written in Swift called &lt;a href=&quot;https://github.com/jessesquires/mariposa&quot;&gt;Mariposa&lt;/a&gt;.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;After writing and publishing a new post, I always share to social media because that is how a lot of folks receive updates (also, &lt;a href=&quot;https://indieweb.org/POSSE&quot;&gt;POSSE&lt;/a&gt;). Because I hate using social media, I have automated this process with a lightweight CLI written in Swift called &lt;a href=&quot;https://github.com/jessesquires/mariposa&quot;&gt;Mariposa&lt;/a&gt;.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;The goal of &lt;a href=&quot;https://github.com/jessesquires/mariposa&quot;&gt;Mariposa&lt;/a&gt; is to replace services like &lt;a href=&quot;https://ifttt.com&quot;&gt;IFTTT&lt;/a&gt; or &lt;a href=&quot;https://zapier.com&quot;&gt;Zapier&lt;/a&gt; to automatically post to &lt;a href=&quot;https://bsky.app&quot;&gt;Bluesky&lt;/a&gt; and &lt;a href=&quot;https://mastodon.social&quot;&gt;Mastodon&lt;/a&gt; whenever I publish a new blog post. Currently, IFTTT has integrations for posting RSS feeds to Twitter and (&lt;a href=&quot;/blog/2022/12/15/rss-to-mastodon/&quot;&gt;with some extra work&lt;/a&gt;) Mastodon, but it does not provide an option for automating RSS to Bluesky. Zapier offers equivalent functionality, and maybe supports Bluesky, but I’m not sure. But enough about third-party services — we don’t need them anymore!&lt;/p&gt;

&lt;h3 id=&quot;motivation&quot;&gt;Motivation&lt;/h3&gt;

&lt;p&gt;I wanted to stop using IFTTT, in general. I also wanted a solution for Bluesky automation which IFTTT does not support. And &lt;a href=&quot;/blog/2023/02/06/goodbye-twitter/&quot;&gt;Twitter is dead&lt;/a&gt;. But more importantly, using IFTTT was always an awkward, roundabout solution anyway. I only used it because I was being lazy.&lt;/p&gt;

&lt;p&gt;My blog is built with &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; (which I’ve &lt;a href=&quot;/blog/tags/jekyll/&quot;&gt;written about before&lt;/a&gt;), and I &lt;a href=&quot;/blog/2017/09/10/building-a-site-with-jekyll-on-nfsn/&quot;&gt;publish using git&lt;/a&gt;. Thus, it was always a bit strange to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push&lt;/code&gt; and publish a new post, visit my website to verify it updated, and then wait for a random service to listen for changes to my RSS feed so it could post to social media for me. If I’m running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push&lt;/code&gt; to publish, I might as well run another command right then that will post to social media. So that’s what I did!&lt;/p&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;

&lt;p&gt;The only prerequisite is that you have a &lt;a href=&quot;https://www.jsonfeed.org&quot;&gt;JSON feed&lt;/a&gt; for your blog. As mentioned, I use &lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt; for my blog, which easily accommodates &lt;a href=&quot;/blog/2017/09/03/supporting-json-feed/&quot;&gt;generating a JSON feed&lt;/a&gt;. But it does not matter how you generate your site, as long as you have a feed.&lt;/p&gt;

&lt;p&gt;If you are only generating a traditional XML RSS feed, you can borrow and adapt &lt;a href=&quot;/blog/2017/09/03/supporting-json-feed/&quot;&gt;my JSON feed template&lt;/a&gt; for your needs. (And if you do not publish a feed at all, well… you should!) While I publish both RSS and JSON feeds, Mariposa only supports JSON, which &lt;a href=&quot;https://developer.apple.com/documentation/foundation/jsondecoder&quot;&gt;comes free with Swift&lt;/a&gt;. And I would rather not ruin something fun by dealing with XML.&lt;/p&gt;

&lt;p&gt;Mariposa reads a JSON feed locally from disk, and then posts the most recent entry to Bluesky and Mastodon. The content of the social media posts includes the blog post title and url. For credentials for Bluesky and Mastodon, you pass in a yaml config file. (I chose yaml for this config because it’s nicer to read and write for this type of data.)&lt;/p&gt;

&lt;p&gt;The great thing about Jekyll (or any static site generator) is its simplicity. The generated site is output to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site/&lt;/code&gt;, which I can preview easily locally. The output includes my full generated JSON feed. That is all the information I need to send out a post to Bluesky or Mastodon — there is no need for services like IFTTT to listen for changes. Much simpler!&lt;/p&gt;

&lt;p&gt;You can find more details and documentation &lt;a href=&quot;https://github.com/jessesquires/mariposa&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;workflow&quot;&gt;Workflow&lt;/h3&gt;

&lt;p&gt;This tool is very much something I built for myself. It is centered around my workflow and my needs. But, I think other folks with similar setups could find it useful. After finishing a blog post, here’s my workflow:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Publish via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Make sure the locally generated site is up-to-date (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll build&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mariposa&lt;/code&gt; to share it via Bluesky and Mastodon&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because I use a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Makefile&lt;/code&gt; with &lt;a href=&quot;https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html&quot;&gt;phony targets&lt;/a&gt; as shortcuts for scripts and commands, the above simplifies to:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make build&lt;/code&gt; (build the site locally)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make pub&lt;/code&gt; (publish changes to web server)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make social&lt;/code&gt; (share post on social media)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;example-usage&quot;&gt;Example usage&lt;/h3&gt;

&lt;p&gt;In this example, I’ve cloned the repo to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Developer/mariposa/&lt;/code&gt;. The config file with my credentials is stored in my home directory at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.mariposa_config.yml&lt;/code&gt;. The command is run from the root directory of my website and my generated feed is stored in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site/feed.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Below is the output for sharing this very blog post!&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;swift run &lt;span class=&quot;nt&quot;&gt;--package-path&lt;/span&gt; ~/Developer/mariposa/ mariposa &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; ~/.mariposa_config.yml &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; _site/feed.json

👀 Preview:
    Mariposa: a lightweight Swift CLI to automate sharing blog posts to social media
    https://www.jessesquires.com/blog/2025/11/07/mariposa/

➡️  Continue? &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;y/N&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;: y

Posting to Bluesky... success ✅
https://bsky.app/profile/jessesquires.com

Posting to Mastodon... success ✅
https://mastodon.social/@jsq/115509326819194206

🎉 Finished
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;developer-notes&quot;&gt;Developer notes&lt;/h3&gt;

&lt;p&gt;I had a lot of fun building this over the course of an afternoon. The &lt;a href=&quot;https://docs.joinmastodon.org/methods/statuses/#create&quot;&gt;Mastodon API&lt;/a&gt; and &lt;a href=&quot;https://docs.bsky.app/docs/category/http-reference&quot;&gt;Bluesky API&lt;/a&gt; were both pretty easy to work with. Many thanks to Manton Reece for &lt;a href=&quot;https://www.manton.org/2023/04/29/getting-started-with.html&quot;&gt;writing this blog post&lt;/a&gt;, which helped me get started even more quickly with the Bluesky API, which was a bit trickier.&lt;/p&gt;

&lt;p&gt;Mariposa is published as a &lt;a href=&quot;https://swiftpackageindex.com/jessesquires/mariposa&quot;&gt;Swift Package&lt;/a&gt;. It depends on JP’s &lt;a href=&quot;https://github.com/jpsim/Yams&quot;&gt;Yams&lt;/a&gt; library for parsing the yaml config, and the &lt;a href=&quot;https://github.com/apple/swift-argument-parser&quot;&gt;swift-argument-parser&lt;/a&gt; for the CLI. This was my first time using swift-argument-parser — and wow, that library is fucking awesome. I barely had to write any code and everything just worked. Other than these two libraries, it’s just Foundation and the Standard Library.&lt;/p&gt;

&lt;p&gt;Again, this is very much a tool I wrote for myself. It supports a very small subset of the Mastodon API and Bluesky API — only the parts I needed. I wrote my own lightweight clients because using a third-party API client library felt like overkill. I made the package very modular, so it should be easy to add features or modify it however you like.&lt;/p&gt;

&lt;p&gt;You probably noticed this is actually &lt;em&gt;partially&lt;/em&gt; automated. I have to manually run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mariposa&lt;/code&gt; after publishing a post. I &lt;em&gt;could&lt;/em&gt; have run this on the web server via a git hook, tracked what entries have already been shared, etc. — but that would have required a lot of additional complexity that was not worth the time or effort to me. You could surely automate fully if desired.&lt;/p&gt;

&lt;p&gt;All the code &lt;a href=&quot;https://github.com/jessesquires/mariposa&quot;&gt;is on GitHub&lt;/a&gt;. Check it out, and feel free to send me a pull request!&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2025/11/07/mariposa/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2025/10/28/workaround-how-to-silence-deprecation-warnings-in-swift/</id>
        <link href="https://www.jessesquires.com/blog/2025/10/28/workaround-how-to-silence-deprecation-warnings-in-swift/" />
        <title>Workaround: how to silence individual deprecation warnings in Swift</title>
        <published>2025-10-28T11:12:34-07:00</published>
        <updated>2025-10-28T11:12:34-07:00</updated>

        <category term="software-dev" />
        <category term="swift" /><category term="xcode" /><category term="compilers" /><category term="clang" />
        <summary type="html">&lt;p&gt;The &lt;a href=&quot;https://www.swift.org&quot;&gt;Swift compiler&lt;/a&gt; has fine-grained controls for compiler warnings. As of Swift 6.2, you can even &lt;a href=&quot;https://useyourloaf.com/blog/treating-warnings-as-errors-in-swift-packages/&quot;&gt;configure these warnings in Swift Packages&lt;/a&gt;. Unfortunately, this is an all-or-nothing approach with no flexibility, unlike the piecemeal control provided by the &lt;a href=&quot;https://clang.llvm.org/get_started.html&quot;&gt;Clang compiler&lt;/a&gt; via &lt;a href=&quot;https://nshipster.com/pragma/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#pragma&lt;/code&gt; directives&lt;/a&gt;.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;The &lt;a href=&quot;https://www.swift.org&quot;&gt;Swift compiler&lt;/a&gt; has fine-grained controls for compiler warnings. As of Swift 6.2, you can even &lt;a href=&quot;https://useyourloaf.com/blog/treating-warnings-as-errors-in-swift-packages/&quot;&gt;configure these warnings in Swift Packages&lt;/a&gt;. Unfortunately, this is an all-or-nothing approach with no flexibility, unlike the piecemeal control provided by the &lt;a href=&quot;https://clang.llvm.org/get_started.html&quot;&gt;Clang compiler&lt;/a&gt; via &lt;a href=&quot;https://nshipster.com/pragma/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#pragma&lt;/code&gt; directives&lt;/a&gt;.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;Like Objective-C, in Swift you can treat all warnings as errors, suppress all warnings, or treat only specific diagnostic groups as warnings or errors. However, in Objective-C — that is, with the Clang compiler — rather than only configure these settings for the entire project, you can &lt;a href=&quot;https://clang.llvm.org/docs/UsersManual.html#controlling-diagnostics-via-pragmas&quot;&gt;control diagnostics via pragmas&lt;/a&gt;. This allows you to piecemeal disable warnings for specific sections of source code. Despite being over a decade old, Swift still has no equivalent.&lt;/p&gt;

&lt;h3 id=&quot;treating-warnings-as-errors&quot;&gt;Treating warnings as errors&lt;/h3&gt;

&lt;p&gt;In nearly every team I have ever worked on — honestly, probably &lt;strong&gt;every&lt;/strong&gt; team — we treat all warnings as errors. The motivation is to avoid accumulating an excessive amount of (maybe mundane and minor) warnings that everyone ignores, which later risks not noticing a warning of serious importance. If all warnings are errors, they must be addressed immediately. A project with zero warnings and zero errors is a wonderful thing.&lt;/p&gt;

&lt;p&gt;There is one exception to this rule — deprecations. Sometimes, we have no choice but to continue using deprecated APIs. This could be due to legacy code that is too difficult to refactor, a lack of adequate replacement APIs, project priorities, etc.&lt;/p&gt;

&lt;p&gt;What we &lt;strong&gt;do not&lt;/strong&gt; want to do is treat all deprecation warnings as errors. That would prevent us from building. We also &lt;strong&gt;do not&lt;/strong&gt; want to suppress &lt;em&gt;all&lt;/em&gt; deprecation warnings via global compiler settings. This would allow new violations to go unnoticed. What we want to do is silence individual deprecated API usage &lt;em&gt;at the call site&lt;/em&gt; on a case-by-case basis. This gives us the best of both worlds: all warnings are treated as errors, and in specific scenarios we allow using deprecated APIs.&lt;/p&gt;

&lt;h3 id=&quot;using-clang-pragma-directives&quot;&gt;Using Clang &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#pragma&lt;/code&gt; directives&lt;/h3&gt;

&lt;p&gt;Let’s look at an example using everyone’s favorite APIs for dealing with everyone’s favorite iOS UI element: the status bar.&lt;/p&gt;

&lt;p&gt;With Clang and Objective-C, we can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#pragma clang diagnostic&lt;/code&gt; to modify warning flags.&lt;/p&gt;

&lt;div class=&quot;language-objc highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#pragma clang diagnostic push
#pragma clang diagnostic ignored &quot;-Wdeprecated-declarations&quot;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIApplication&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sharedApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setStatusBarHidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;withAnimation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIStatusBarAnimationFade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#pragma clang diagnostic pop
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this example, we are using the deprecated API &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiapplication/setstatusbarhidden(_:with:)?language=objc&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setStatusBarHidden:withAnimation:&lt;/code&gt;&lt;/a&gt;. Prior to the call, we save the diagnostic state using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clang diagnostic push&lt;/code&gt;, then ignore the warning for deprecated declarations (which would actually be treated as an error). After the call, we restore the previous diagnostic state using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clang diagnostic pop&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;swift-workaround&quot;&gt;Swift workaround&lt;/h3&gt;

&lt;p&gt;Disappointingly, Swift has no equivalent to &lt;a href=&quot;https://nshipster.com/pragma/&quot;&gt;Clang &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#pragma&lt;/code&gt; directives&lt;/a&gt;. Fortunately, we can do some hacky things with protocols to achieve the same results. (Credit is due to the &lt;a href=&quot;https://stackoverflow.com/questions/31540446/how-to-silence-a-warning-in-swift/45743766#45743766&quot;&gt;StackOverflow user who offered this workaround&lt;/a&gt;. This blog post serves to elaborate more on what, why, and how.)&lt;/p&gt;

&lt;p&gt;Alternatively, instead of using this Swift workaround, you can simply use Objective-C and Clang &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#pragma&lt;/code&gt; and take advantage of the interoperability between the two languages. This hack is for folks that do not want to introduce Objective-C into their Swift-only codebase.&lt;/p&gt;

&lt;p&gt;First, we define a new protocol that offers the same APIs as the deprecated ones. You need to name the protocol members slightly differently than the original APIs. I recommend using a traditional Objective-C three-letter prefix. (In this example, I’m using “deprecated_”.)&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DeprecatedStatusBarMethods&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;deprecated_setStatusBarHidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;hidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIStatusBarAnimation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, have the type that owns the deprecated APIs conform to this protocol. The implementation will simply call the deprecated method. In this case, &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiapplication/setstatusbarhidden(_:with:)?language=objc&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setStatusBarHidden:withAnimation:&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DeprecatedStatusBarMethods&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// tell compiler this is deprecated, too.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// this would otherwise produce a warning.&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;@available&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deprecated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;deprecated_setStatusBarHidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;hidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIStatusBarAnimation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// call the original deprecated method&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setStatusBarHidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that you must mark the protocol implementation in the extension as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@available(*, deprecated)&lt;/code&gt; to silence the compiler deprecation warning. Without this annotation, you would see a warning when calling the original deprecated API.&lt;/p&gt;

&lt;p&gt;With those pieces in place, we can call the deprecated API by first casting the type to the protocol type we just defined. This successfully calls the deprecated API and produces no warnings or errors, despite using the Swift compiler flag &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-warnings-as-errors&lt;/code&gt; to treat all warnings as errors.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// no warnings!&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DeprecatedStatusBarMethods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;deprecated_setStatusBarHidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isHidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is obviously very verbose. To mitigate this, you could wrap this in a helper method.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;myApp_setStatusBarHidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DeprecatedStatusBarMethods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;deprecated_setStatusBarHidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// usage:&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;myApp_setStatusBarHidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Importantly, you cannot directly call the implementation method — the one we annotated with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@available(*, deprecated)&lt;/code&gt;. Doing this results in the compiler producing a warning, as you would expect.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// warning: deprecated API usage&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;deprecated_setStatusBarHidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isHidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;how-does-this-even-work&quot;&gt;How does this even work?&lt;/h3&gt;

&lt;p&gt;I am not a Swift compiler expert, but here is my best attempt at an explanation. Hopefully everything up until the call site makes sense, but I will cover that too.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;In the implementation of the protocol, we annotate the API using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@available(*, deprecated)&lt;/code&gt;. Without this annotation, the compiler produces a warning at the call site of the original deprecated API. Explicitly annotating the outer declaration &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;func deprecated_setStatusBarHidden(_:with:)&lt;/code&gt; as deprecated silences the deprecation warning inside the method implementation. Because this is explicit, the compiler does not need to warn us.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Calling a deprecated method produces a deprecation warning. As noted above, when we call the newly added deprecated method directly on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIApplication.shared&lt;/code&gt;, we see warning. This is expected, the method is annotated with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@available(*, deprecated)&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;When we write &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(UIApplication.shared as DeprecatedStatusBarMethods)&lt;/code&gt;, we are hiding the concrete type. Then, we are allowed to call the method on the protocol type without any warnings because &lt;em&gt;that&lt;/em&gt; method has no annotations. The compiler will lookup the method in the protocol witness table rather than directly in the concrete type. Essentially, we have performed a kind of “type erasure” and are hiding our deprecated code behind the protocol witness table.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;is-this-practical&quot;&gt;Is this practical?&lt;/h3&gt;

&lt;p&gt;This workaround is rather verbose and somewhat obscure (and kind of obtuse) — but it works. If your project already has a mix of Objective-C and Swift, you are probably better off silencing deprecation warnings using Clang pragmas. If you want to avoid adding Objective-C, this is a viable alternative. However, if your project is using a lot of deprecated APIs, I can see this workaround being quite cumbersome. Of course, the best solution is to stop using deprecated APIs altogether — but, like many aspects of software development, we do not always have that luxury.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2025/10/28/workaround-how-to-silence-deprecation-warnings-in-swift/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2025/03/24/automate-perfect-mac-screenshots/</id>
        <link href="https://www.jessesquires.com/blog/2025/03/24/automate-perfect-mac-screenshots/" />
        <title>How to automate perfect screenshots for the Mac App Store</title>
        <published>2025-03-24T08:21:37-07:00</published>
        <updated>2025-03-24T08:21:37-07:00</updated>

        <category term="software-dev" />
        <category term="xcode" /><category term="macos" /><category term="mac-app-store" /><category term="automation" /><category term="screenshots" /><category term="ui-testing" />
        <summary type="html">&lt;p&gt;Meeting the &lt;a href=&quot;/blog/2024/01/16/app-store-screenshot-requirements/&quot;&gt;requirements for screenshots&lt;/a&gt; is a frustrating experience. On iOS, tooling like &lt;a href=&quot;https://github.com/shinydevelopment/SimulatorStatusMagic&quot;&gt;SimulatorStatusMagic&lt;/a&gt;, &lt;a href=&quot;https://github.com/jessesquires/nine41&quot;&gt;Nine41&lt;/a&gt;, and &lt;a href=&quot;https://docs.fastlane.tools/actions/snapshot/&quot;&gt;fastlane snapshot&lt;/a&gt; help make the process easier. However, on macOS there is much less support (and, sadly, demand) for automated tooling — so you are kind of on your own to figure it out. I spent some time recently solving this process for myself. I want to share how I have managed to automate perfect screenshots for the Mac App Store.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;Meeting the &lt;a href=&quot;/blog/2024/01/16/app-store-screenshot-requirements/&quot;&gt;requirements for screenshots&lt;/a&gt; is a frustrating experience. On iOS, tooling like &lt;a href=&quot;https://github.com/shinydevelopment/SimulatorStatusMagic&quot;&gt;SimulatorStatusMagic&lt;/a&gt;, &lt;a href=&quot;https://github.com/jessesquires/nine41&quot;&gt;Nine41&lt;/a&gt;, and &lt;a href=&quot;https://docs.fastlane.tools/actions/snapshot/&quot;&gt;fastlane snapshot&lt;/a&gt; help make the process easier. However, on macOS there is much less support (and, sadly, demand) for automated tooling — so you are kind of on your own to figure it out. I spent some time recently solving this process for myself. I want to share how I have managed to automate perfect screenshots for the Mac App Store.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;h3 id=&quot;screenshot-composition&quot;&gt;Screenshot composition&lt;/h3&gt;

&lt;p&gt;In a &lt;a href=&quot;/blog/2024/01/16/app-store-screenshot-requirements/&quot;&gt;previous post&lt;/a&gt;, I discussed how you have two basic “formats” or compositions for Mac app screenshots. The first is taking a screenshot of your app in fullscreen mode, capturing only the app window. The second is capturing your app running on a full Mac desktop where the app is not in fullscreen — that is, capturing the entire desktop with your app window(s), the menu bar, dock, and desktop background photo.&lt;/p&gt;

&lt;p&gt;Fullscreen Mac app screenshots are much simpler and easier. All you have to do is screenshot your app window and you’re finished. My focus for this post (and my goal for my screenshots) is to capture full Mac desktop screenshots. Similar to iOS, I want a “perfect” menu bar with no extra icons and a clock showing &lt;a href=&quot;https://github.com/jessesquires/nine41&quot;&gt;9:41 AM&lt;/a&gt;, and a nice desktop background photo.&lt;/p&gt;

&lt;h3 id=&quot;current-options-for-mac-app-screenshots&quot;&gt;Current options for Mac app screenshots&lt;/h3&gt;

&lt;p&gt;Unfortunately, fastlane snapshot simply does not work for macOS. (See &lt;a href=&quot;https://github.com/fastlane/fastlane/issues/11092&quot;&gt;#11092&lt;/a&gt;, &lt;a href=&quot;https://github.com/fastlane/fastlane/issues/11092#issuecomment-349012721&quot;&gt;#11092-comment-349012721&lt;/a&gt;, &lt;a href=&quot;https://github.com/fastlane/fastlane/issues/11092#issuecomment-349273411&quot;&gt;#11092-comment-349273411&lt;/a&gt;, &lt;a href=&quot;https://github.com/fastlane/fastlane/pull/19864&quot;&gt;#19864&lt;/a&gt;) You could probably get it working on your own, but the maintainers seem uninterested in dedicated long-term support for macOS. So I decided to look for alternatives.&lt;/p&gt;

&lt;p&gt;Another possibility is to manually take screenshots of your Mac app and optionally bring those into Photoshop, Acorn, Gimp, or any other image editor for additional editing. This was my approach before, but I found it to be too tedious — even when using some Photoshop templates to make it easier. I need something that can be fully (or mostly) automated.&lt;/p&gt;

&lt;h3 id=&quot;automatic-screenshots-via-xcode&quot;&gt;Automatic screenshots via Xcode&lt;/h3&gt;

&lt;p&gt;As of Xcode 9, &lt;a href=&quot;https://developer.apple.com/documentation/XCUIAutomation&quot;&gt;Xcode UITest&lt;/a&gt; allows you to &lt;a href=&quot;https://developer.apple.com/documentation/xctest/xcuiscreenshot&quot;&gt;capture screenshots&lt;/a&gt; during tests. This is the API that fastlane uses under-the-hood for iOS. Again, because fastlane is not an option for macOS, you can implement this yourself. Xcode makes this process rather simple and easy. Here’s a helper method that saves a screenshot.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;saveScreenshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;screenshot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;windows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;main-window&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;screenshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;attachment&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCTAttachment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;uniformTypeIdentifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;public.png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;screenshot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pngRepresentation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;userInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;attachment&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lifetime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keepAlways&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attachment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// usage&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;saveScreenshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;screenshot-01&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can write normal UI tests and take screenshots when desired. When the test suite completes, you can navigate to the test results window in Xcode, find the screenshots, and save them.&lt;/p&gt;

&lt;p&gt;This solves the problem of automating generating the raw screenshots. Unfortunately, once you have these raw screenshots, you will need to do some post-processing. The screenshots from XCUITest have ugly artifacts in the window corners. They are not correctly cropped and clipped with a transparent background, like when you take a screenshot via the built-in functionality in macOS. See the image below for an example.&lt;/p&gt;

&lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;col&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;d-flex justify-content-center col-12 &quot;&gt;
        &lt;figure&gt;
            &lt;img class=&quot;img-thumbnail img-fluid&quot; src=&quot;/img/blog/mac-screenshot-process.jpg&quot; title=&quot;Screenshot images from Xcode and and macOS&quot; alt=&quot;Screenshot images from Xcode and and macOS&quot; /&gt;
            &lt;div class=&quot;row&quot;&gt;
                &lt;div class=&quot;col-12 d-flex justify-content-center&quot;&gt;
                
                    &lt;figcaption&gt;
                        &lt;p&gt;&lt;small class=&quot;text-muted text-center&quot;&gt;
                            From left to right: raw output from Xcode UI Test, the same image after processing, the output from taking a screenshot in macOS. 
                        &lt;/small&gt;&lt;/p&gt;
                    &lt;/figcaption&gt;
                
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/figure&gt;
    &lt;/div&gt;
    &lt;div class=&quot;col&quot;&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The first image is what screenshotting via Xcode produces — ugly corners that will display whatever is in the background when your app runs via the UI test suite. The second image is the result after manually processing the image to remove the artifacts. The final image is the output of taking a standard screenshot on macOS. As you can see, my processing is &lt;em&gt;not quite exact&lt;/em&gt;, but it is close enough.&lt;/p&gt;

&lt;h3 id=&quot;post-processing-xcode-screenshots&quot;&gt;Post-processing Xcode screenshots&lt;/h3&gt;

&lt;p&gt;You could use an image editor like Photoshop, Acorn, or Gimp to round off these corners, but that is quite tedious. I used to use Photoshop with a smart object that would do this. However, that’s still quite manual. The best tool for this is &lt;a href=&quot;https://flyingmeat.com/retrobatch/&quot;&gt;Retrobatch&lt;/a&gt; from Gus Mueller — and &lt;strong&gt;it is incredible&lt;/strong&gt;. I love this app.&lt;/p&gt;

&lt;p&gt;Using Retrobatch, I created a workflow to round the corners. Through trial and error, I arrived at a corner radius of 24 points. Again, not &lt;em&gt;quite&lt;/em&gt; exact (see screenshot above), but close enough for our purposes here. Windows in macOS also have a subtle 1-point border that have a hex color value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#D9D9D9&lt;/code&gt; in light mode and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#636363&lt;/code&gt; in dark mode. Retrobatch can also take care of this.&lt;/p&gt;

&lt;p&gt;This gives us screenshots of our app windows that are (mostly) equivalent to taking screenshots manually via macOS. First, we use Xcode UITests to get the raw screenshots, then we drop them into Retrobatch for this clean up task. Now we need to get our desktop background.&lt;/p&gt;

&lt;h3 id=&quot;perfect-menu-bar-and-desktop-background&quot;&gt;Perfect menu bar and desktop background&lt;/h3&gt;

&lt;p&gt;To get a perfect Mac desktop background, I created a new account on my Mac to avoid having to change any of my own preferences. In the fresh account, I set the wallpaper, configured the menu bar, and set the time manually to 9:41 AM, and then took a screenshot. Here’s an example using Finder as the active app. However, for each of my apps I will install them and make them active so their name displays in the menu bar.&lt;/p&gt;

&lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;col&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;d-flex justify-content-center col-12 &quot;&gt;
        &lt;figure&gt;
            &lt;img class=&quot;img-thumbnail img-fluid&quot; src=&quot;/img/blog/mac-screenshot-background.jpg&quot; title=&quot;Blank Mac desktop screenshot background&quot; alt=&quot;Blank Mac desktop screenshot background&quot; /&gt;
            &lt;div class=&quot;row&quot;&gt;
                &lt;div class=&quot;col-12 d-flex justify-content-center&quot;&gt;
                
                    &lt;figcaption&gt;
                        &lt;p&gt;&lt;small class=&quot;text-muted text-center&quot;&gt;
                            Blank Mac desktop screenshot background 
                        &lt;/small&gt;&lt;/p&gt;
                    &lt;/figcaption&gt;
                
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/figure&gt;
    &lt;/div&gt;
    &lt;div class=&quot;col&quot;&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I use the amazing &lt;a href=&quot;https://hector.me/aqueux&quot;&gt;Aqueux wallpapers by Hector Simpson&lt;/a&gt; — a wonderful throwback to Mac OS X Tiger. (Back when macOS was actually good.) Note you’ll need to create these backgrounds in both light mode and dark mode if you provide screenshots in both modes for your app.&lt;/p&gt;

&lt;h3 id=&quot;the-final-product&quot;&gt;The final product&lt;/h3&gt;

&lt;p&gt;Now we have all the pieces we need: the app window screenshots and the desktop background. Before, I was using a Photoshop template to assemble these. Again, that’s too tedious. This is where Retrobatch &lt;em&gt;really shines&lt;/em&gt;. Below is a screenshot of my entire Retrobatch workflow.&lt;/p&gt;

&lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;col&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;d-flex justify-content-center col-12 &quot;&gt;
        &lt;figure&gt;
            &lt;img class=&quot;img-thumbnail img-fluid&quot; src=&quot;/img/blog/mac-screenshot-retrobatch.jpg&quot; title=&quot;Retrobatch workflow&quot; alt=&quot;Retrobatch workflow&quot; /&gt;
            &lt;div class=&quot;row&quot;&gt;
                &lt;div class=&quot;col-12 d-flex justify-content-center&quot;&gt;
                
                    &lt;figcaption&gt;
                        &lt;p&gt;&lt;small class=&quot;text-muted text-center&quot;&gt;
                            Retrobatch workflow 
                        &lt;/small&gt;&lt;/p&gt;
                    &lt;/figcaption&gt;
                
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/figure&gt;
    &lt;/div&gt;
    &lt;div class=&quot;col&quot;&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Here is what’s happening:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The raw screenshots from Xcode are read from the specified input folder.&lt;/li&gt;
  &lt;li&gt;The corners get rounded correctly.&lt;/li&gt;
  &lt;li&gt;It checks if the filename contains “dark”. This is part of my file naming scheme. For screenshots in dark mode, I append “-dark”. The workflow branches based on if the screenshots are in light mode or dark mode, but the steps are equivalent.&lt;/li&gt;
  &lt;li&gt;The 1-point border is added using the correct color based on light mode or dark mode.&lt;/li&gt;
  &lt;li&gt;A subtle drop shadow gets added, roughly equivalent to how macOS renders. Again through trial and error, I arrived at a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0,-50)&lt;/code&gt; offset, a 70-point blur radius, and 60% opacity.&lt;/li&gt;
  &lt;li&gt;Next is the overlay step. This overlays the app window screenshot over the desktop background and centers it. The light mode and dark mode branches of the workflow have their respective light and dark desktop backgrounds.&lt;/li&gt;
  &lt;li&gt;The metadata is deleted, to reduce file size and unnecessary data.&lt;/li&gt;
  &lt;li&gt;The final images get written to the specified output folder. These images are the full resolution of my 14-inch M3 MacBook Pro desktop. (I use these screenshots to embed in device frames for promotional images on my website.)&lt;/li&gt;
  &lt;li&gt;There is one final branch that resizes and crops all screenshots to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2880x1800 px&lt;/code&gt; because of the
&lt;a href=&quot;/blog/2024/01/16/app-store-screenshot-requirements/&quot;&gt;archaic requirements&lt;/a&gt; of App Store Connect. The Mac App Store does not accept screenshots using the default resolutions of modern M-series MacBooks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that’s everything. There was some upfront effort to put all of this together, but now my workflow for generating Mac app screenshots is incredibly simple with minimal manual steps.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Run my Xcode UI test suite to generate the screenshots.&lt;/li&gt;
  &lt;li&gt;Extract the screenshots from the test results and save them to my Retrobatch input folder.&lt;/li&gt;
  &lt;li&gt;Run the Retrobatch workflow, which saves everything to the output folder.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s an example of the final product using my app, &lt;a href=&quot;https://hexedbits.com/taxatio/&quot;&gt;Taxatio&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;col&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;d-flex justify-content-center col-12 &quot;&gt;
        &lt;figure&gt;
            &lt;img class=&quot;img-thumbnail img-fluid&quot; src=&quot;/img/blog/mac-screenshot-finished.jpg&quot; title=&quot;Finished screenshot for Taxatio&quot; alt=&quot;Finished screenshot for Taxatio&quot; /&gt;
            &lt;div class=&quot;row&quot;&gt;
                &lt;div class=&quot;col-12 d-flex justify-content-center&quot;&gt;
                
                    &lt;figcaption&gt;
                        &lt;p&gt;&lt;small class=&quot;text-muted text-center&quot;&gt;
                            Finished screenshot for Taxatio 
                        &lt;/small&gt;&lt;/p&gt;
                    &lt;/figcaption&gt;
                
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/figure&gt;
    &lt;/div&gt;
    &lt;div class=&quot;col&quot;&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2025/03/24/automate-perfect-mac-screenshots/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2025/03/10/swiftpm-schemes-in-xcode/</id>
        <link href="https://www.jessesquires.com/blog/2025/03/10/swiftpm-schemes-in-xcode/" />
        <title>How to remove unwanted Swift Package schemes in Xcode</title>
        <published>2025-03-10T10:16:17-07:00</published>
        <updated>2025-03-10T10:16:17-07:00</updated>

        <category term="software-dev" />
        <category term="xcode" /><category term="ios" /><category term="macos" /><category term="swift" /><category term="swiftpm" />
        <summary type="html">&lt;p&gt;Xcode automatically creates schemes for your app and other targets included in your project, which allow you to build and run those targets. I recently ran into an issue where Xcode was including schemes from third-party Swift Package dependencies in its auto-populated list. This automatic behavior does not cause any problems, but it can be quite annoying and distracting to have unwanted schemes from packages pollute your list of schemes.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;Xcode automatically creates schemes for your app and other targets included in your project, which allow you to build and run those targets. I recently ran into an issue where Xcode was including schemes from third-party Swift Package dependencies in its auto-populated list. This automatic behavior does not cause any problems, but it can be quite annoying and distracting to have unwanted schemes from packages pollute your list of schemes.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;This issue &lt;a href=&quot;https://developer.apple.com/forums/thread/716024&quot;&gt;has been reported in the developer forums&lt;/a&gt;. Apparently, if a Swift package includes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.swiftpm/&lt;/code&gt; directory with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.xcscheme&lt;/code&gt; files for its own project development, then Xcode will automatically detect and display these schemes in the dropdown list of schemes for your project. This is rather undesirable. It’s an especially frustrating experience because even if you delete them from the list in Xcode, they will eventually reappear when packages get updated or refreshed. For example, you’ll experience this issue with the popular library, &lt;a href=&quot;https://github.com/CocoaLumberjack/CocoaLumberjack&quot;&gt;CocoaLumberjack&lt;/a&gt;, which includes schemes using &lt;a href=&quot;https://github.com/CocoaLumberjack/CocoaLumberjack/tree/master/.swiftpm/xcode/xcshareddata/xcschemes&quot;&gt;a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.swiftpm/&lt;/code&gt; directory here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://developer.apple.com/forums/thread/716024?answerId=735186022#735186022&quot;&gt;solution&lt;/a&gt; to preventing these package schemes from appearing in Xcode automatically is for package authors to switch to using an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.xcworkspace&lt;/code&gt; file for their schemes, rather than a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.swiftpm/&lt;/code&gt; directory. Here’s an example from the &lt;a href=&quot;https://github.com/sideeffect-io/AsyncExtensions/pull/29&quot;&gt;sideeffect.io/AsyncExtensions&lt;/a&gt; package.&lt;/p&gt;

&lt;p&gt;Curiously, I have also seen schemes appear from packages that use neither &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.xcworkspace&lt;/code&gt; nor &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.swiftpm&lt;/code&gt;, but just a normal &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.xcodeproj&lt;/code&gt;. Why &lt;em&gt;those&lt;/em&gt; schemes appear automatically in Xcode is a mystery to me.&lt;/p&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;p&gt;You may not always be able to get package authors to make this change. While you wait for library maintainers to implement changes or accept a pull request (if they ever do), you can use my workaround to delete them from your projects.&lt;/p&gt;

&lt;p&gt;Unfortunately, working with Swift packages in Xcode remains a cumbersome experience. Unlike CocoaPods — which adopted the prior art developed by established package managers in other ecosystems by using a specification file and storing package sources in a directory relative to your project — Xcode forces you use an opaque UI to add package dependencies. Even worse, rather than store packages relative to your project, when you add Swift packages to your Xcode project they get stored in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Library/Developer/Xcode/DerivedData/&amp;lt;YOUR_APP_NAME-SOME_UUID&amp;gt;/SourcePackages/checkouts/&lt;/code&gt;. These implementation details along with the lack of the equivalent of &lt;a href=&quot;https://guides.cocoapods.org/syntax/podfile.html#post_install&quot;&gt;CocoaPods post-install hooks&lt;/a&gt; make workarounds more challenging. SwiftPM integration in Xcode was designed by people who think very different. But it’s ok — we can workaround this annoyance, too.&lt;/p&gt;

&lt;p&gt;Here’s a script that will delete all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.xcscheme&lt;/code&gt; files for packages added to your Xcode project. These schemes will then no longer appear in your scheme list.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Deletes unwanted schemes from SwiftPM packages&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# to prevent them from being listed in Xcode.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# If a Swift Package includes `.swiftpm/xcode/xcshareddata/xcschemes/`,&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# then the schemes will show up in Xcode automatically.&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-eu&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;SCHEME_DIRS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;~/Library/Developer/Xcode/DerivedData/YOUR_APP_NAME&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/SourcePackages/checkouts/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/.swiftpm/xcode/xcshareddata/xcschemes
&lt;span class=&quot;nv&quot;&gt;all_dirs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$SCHEME_DIRS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;each &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;all_dirs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[@]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do
  if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$each&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rf&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$each&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rmdir&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$each&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;fi
done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You’ll need to either run this script somewhere as part of your build process, or manually invoke it as needed. For example, if your team uses a &lt;a href=&quot;https://github.com/yonaskolb/XcodeGen&quot;&gt;XcodeGen&lt;/a&gt; or a similar tool, you’ll want to run this script after rebuilding your Xcode project. Alternatively, you could add a &lt;em&gt;Build Script Phase&lt;/em&gt; to your project that executes the script.&lt;/p&gt;

&lt;p&gt;This is not a perfect solution, but it should save you some time.&lt;/p&gt;

&lt;p&gt;A few notes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You’ll need to replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;YOUR_APP_NAME&lt;/code&gt; with the name of your Xcode project.&lt;/li&gt;
  &lt;li&gt;If you have multiple projects with the exact same name, this script will match both paths (because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;YOUR_APP_NAME*/&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;Similarly, if you have multiple git checkouts of the same project, this script will match all of them.&lt;/li&gt;
  &lt;li&gt;All packages are captured by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/SourcePackages/checkouts/*/&lt;/code&gt;, so there’s no need to enumerate the specific packages causing the problem. This is also convenient because it will catch any future packages that exhibit the same problem.&lt;/li&gt;
  &lt;li&gt;I tried to use Xcode environment variables to grab the project’s specific &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DerivedData/&lt;/code&gt; directory, but none of those work. I tried &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BUILD_DIR&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BUILD_ROOT&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PROJECT_TEMP_DIR&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TARGET_TEMP_DIR&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DERIVED_FILE_DIR&lt;/code&gt;. None of them return the correct directory in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Library/&lt;/code&gt;, but instead return directories relative to your Xcode project that do not exist.&lt;/li&gt;
  &lt;li&gt;After the script runs for the first time, the schemes may still appear in the list. Restarting Xcode should remove them, or you can manually remove them.&lt;/li&gt;
  &lt;li&gt;As mentioned above, when packages are updated or redownloaded, the schemes will reappear, so you’ll need to re-run the script after packages are refreshed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’ve been annoyed by random package schemes being listed in your Xcode project, I hope this helps!&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2025/03/10/swiftpm-schemes-in-xcode/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2025/03/04/ai-code-review/</id>
        <link href="https://www.jessesquires.com/blog/2025/03/04/ai-code-review/" />
        <title>AI Code review is always wrong</title>
        <published>2025-03-04T12:44:26-08:00</published>
        <updated>2025-03-04T12:44:26-08:00</updated>

        <category term="software-dev" />
        <category term="ai" /><category term="politics" /><category term="automation" /><category term="code-review" />
        <summary type="html">&lt;p&gt;I work on a team that has enabled an AI code review tool. And so far, I am unimpressed. Every single time, the code review comments the AI bot leaves on my pull requests are not just wrong, but laughably wrong. When its suggestions are not completely fucking incorrect, they make no sense at all.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;I work on a team that has enabled an AI code review tool. And so far, I am unimpressed. Every single time, the code review comments the AI bot leaves on my pull requests are not just wrong, but laughably wrong. When its suggestions are not completely fucking incorrect, they make no sense at all.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;For example, in Swift you can use &lt;a href=&quot;https://docs.swift.org/swift-book/documentation/the-swift-programming-language/optionalchaining/&quot;&gt;optional-chaining&lt;/a&gt; or you can unwrap optionals.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Job&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;john&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;John&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Programmer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Non-optional: access directly&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;john&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Optional-chaining: maybe nil&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;jobTitle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;john&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Unwrapped optional&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;job&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;john&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;job is nil&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On one of my pull requests, the AI bot recently suggested: &lt;em&gt;“Consider using optional chaining to safely handle nil values. This prevents potential crashes if the value is nil while maintaining the same logical flow.”&lt;/em&gt; This would be a good suggestion if you wrote code that force-unwrapped an optional.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Force-unwrapped: will crash if nil&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;jobTitle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;john&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Not only were no values force-unwrapped in the code, but the property the bot mentioned was &lt;strong&gt;&lt;em&gt;not&lt;/em&gt; optional&lt;/strong&gt;. The code in question contained two similarly named properties. One of them was optional, one of them was not. The optional property was being unwrapped correctly. The non-optional property was being accessed directly and thus — by definition — did not need to be unwrapped. The bot fundamentally misunderstood this entire concept.&lt;/p&gt;

&lt;p&gt;But this is worse than being wrong, because the bot writes with so much conviction and assurance. If I were early in my career, perhaps an intern or a student still learning, I might be persuaded by its imitation intelligence and counterfeit confidence. LLMs are not “intelligent” — they are statistical regurgitation machines whose core features are plagiarism and hallucinations, &lt;em&gt;at best&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These contextless, pretend code reviews are wasting my time.&lt;/strong&gt; What are we even doing in this industry? Isn’t every corporation in Silicon Valley jam-packed with “the smartest people in the world”? Doesn’t every corporation in Silicon Valley have “job offer rates more competitive than Stanford acceptance rates”? Why can’t one of those people give me a goddamn code review?&lt;/p&gt;

&lt;p&gt;Fad-driven development and anti-thought is our new gospel.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2025/03/04/ai-code-review/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2025/02/24/xcode-tip-spell-check/</id>
        <link href="https://www.jessesquires.com/blog/2025/02/24/xcode-tip-spell-check/" />
        <title>Xcode Tip: spell checking</title>
        <published>2025-02-24T07:59:46-08:00</published>
        <updated>2025-02-24T07:59:46-08:00</updated>

        <category term="software-dev" />
        <category term="xcode" /><category term="ios" /><category term="macos" /><category term="xcode-tips" />
        <summary type="html">&lt;p&gt;Did you know that Xcode can spell check your code and comments? Based on my experience working on large teams and large Xcode projects, this is a little-known feature. I routinely find spelling errors, not only in code comments but in symbol names. For the latter, this is particularly frustrating when a misspelled symbol is widely used because correcting that error — a rename that affects a substantial portion of the codebase — produces a large diff. Once you notice that your entire team has been passing around a &lt;em&gt;“databaesQuery”&lt;/em&gt; for six months, it will drive you insane until fixed.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;Did you know that Xcode can spell check your code and comments? Based on my experience working on large teams and large Xcode projects, this is a little-known feature. I routinely find spelling errors, not only in code comments but in symbol names. For the latter, this is particularly frustrating when a misspelled symbol is widely used because correcting that error — a rename that affects a substantial portion of the codebase — produces a large diff. Once you notice that your entire team has been passing around a &lt;em&gt;“databaesQuery”&lt;/em&gt; for six months, it will drive you insane until fixed.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;On large teams, large diffs are difficult to get reviewed. But more importantly, the probability for merge conflicts is high. Nothing is worse than having to resolve conflicts dozens of times before you are able to merge. A large rename to correct a spelling error can end up being the same experience has trying to merge a significant refactor — a multi-day effort. That’s not ideal.&lt;/p&gt;

&lt;p&gt;Lucky for us, it’s possible to (help) prevent typos from ever getting merged in the first place. In Xcode, you can enable spelling from the Edit menu, &lt;em&gt;Edit &amp;gt; Format &amp;gt; Spelling and Grammar &amp;gt; Check Spelling While Typing&lt;/em&gt;. Just like a typical word processor, Xcode will helpfully underline misspelled words and offer to apply corrections.&lt;/p&gt;

&lt;p&gt;Even better, Xcode understands variable names and will correctly identify errors in &lt;em&gt;camelCase&lt;/em&gt;, &lt;em&gt;snake_case&lt;/em&gt;, and other common identifier formats. Xcode also understands programming language keywords and syntax, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;func&lt;/code&gt;, for example, will not be identified as a spelling error. You can right-click to apply corrections. You can also add words to the dictionary via &lt;em&gt;Right Click &amp;gt; Learn Spelling&lt;/em&gt;, which can be useful for domain-specific abbreviations or other non-dictionary terms that are &lt;em&gt;not&lt;/em&gt; actual spelling errors.&lt;/p&gt;

&lt;p&gt;The only caveat here is that everyone on your team must manually apply spelling corrections. However, perhaps this could be solved with a linter rule or Xcode extension.&lt;/p&gt;

&lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;col&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;d-flex justify-content-center col-12 &quot;&gt;
        &lt;figure&gt;
            &lt;img class=&quot;img-thumbnail img-fluid&quot; src=&quot;/img/blog/xcode-spell-check.jpg&quot; title=&quot;Xcode spell checking&quot; alt=&quot;Xcode spell checking&quot; /&gt;
            &lt;div class=&quot;row&quot;&gt;
                &lt;div class=&quot;col-12 d-flex justify-content-center&quot;&gt;
                
                    &lt;figcaption&gt;
                        &lt;p&gt;&lt;small class=&quot;text-muted text-center&quot;&gt;
                            Xcode spell checking 
                        &lt;/small&gt;&lt;/p&gt;
                    &lt;/figcaption&gt;
                
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/figure&gt;
    &lt;/div&gt;
    &lt;div class=&quot;col&quot;&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2025/02/24/xcode-tip-spell-check/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2025/02/20/misery-boredom-anxiety/</id>
        <link href="https://www.jessesquires.com/blog/2025/02/20/misery-boredom-anxiety/" />
        <title>Reading Notes: From misery to boredom to anxiety</title>
        <published>2025-02-20T11:28:05-08:00</published>
        <updated>2025-02-20T11:28:05-08:00</updated>

        <category term="reading-notes" />
        <category term="capitalism" /><category term="labor" /><category term="politics" />
        <summary type="html">&lt;p&gt;The zine, &lt;em&gt;We Are All Very Anxious: Six Theses on Anxiety and Why It is Effectively Preventing Militancy, and One Possible Strategy for Overcoming It&lt;/em&gt;, was published by The Institute for Precarious Consciousness and CrimethInc in 2014. (&lt;a href=&quot;https://theanarchistlibrary.org/library/institute-for-precarious-consciousness-we-are-all-very-anxious&quot;&gt;Anarchist Library&lt;/a&gt;, &lt;a href=&quot;https://cdn.crimethinc.com/pdfs/We-Are-All-Very-Anxious.pdf&quot;&gt;Zine PDF&lt;/a&gt;). I decided to read it again recently because it felt relevant to the current political moment in the US. It is as prescient as ever.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;The zine, &lt;em&gt;We Are All Very Anxious: Six Theses on Anxiety and Why It is Effectively Preventing Militancy, and One Possible Strategy for Overcoming It&lt;/em&gt;, was published by The Institute for Precarious Consciousness and CrimethInc in 2014. (&lt;a href=&quot;https://theanarchistlibrary.org/library/institute-for-precarious-consciousness-we-are-all-very-anxious&quot;&gt;Anarchist Library&lt;/a&gt;, &lt;a href=&quot;https://cdn.crimethinc.com/pdfs/We-Are-All-Very-Anxious.pdf&quot;&gt;Zine PDF&lt;/a&gt;). I decided to read it again recently because it felt relevant to the current political moment in the US. It is as prescient as ever.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;The current fascist takeover of the US government and public institutions is marked primarily by the overwhelming feeling of &lt;strong&gt;anxiety&lt;/strong&gt; — that is, &lt;em&gt;precarity&lt;/em&gt;. The ruling class manufactures and perpetuates anxiety and precarity through chaos, misinformation, disinformation, the surreptitious dismantling of the public safety net, and disorderly reversals of established precedents and norms. We can observe this happening each week.&lt;/p&gt;

&lt;p&gt;Unfortunately, a large contingent of the working class has been hoodwinked into thinking that the source of their anxiety and precarity is from within their own cohort, rather than deliberately imposed and maintained by the counterfeit populists of the fascist ruling class. As long as we remain anxious, under the oppressive thumb of unelected, extractive billionaires and seedy politicians, resistance against state repression will remain difficult, if not impossible.&lt;/p&gt;

&lt;p&gt;I wish I had answers. Until we’re able to effectively counteract our precariousness, let’s hope our peers in the working class wake up soon and realize that immigrants and trans kids are not the problem — and never were.&lt;/p&gt;

&lt;p&gt;I highly recommend reading the full zine, it is not very long. Here are my highlights.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Each phase of capitalism has a particular affect which holds it together. This is not a static situation. The prevalence of a particular dominant affect is sustainable only until strategies of resistance able to break down this particular affect and/or its social sources are formulated. Hence, capitalism constantly comes into crisis and recomposes around newly dominant affects.”&lt;/p&gt;

  &lt;p&gt;[…]&lt;/p&gt;

  &lt;p&gt;“Each phase blames the system’s victims for the suffering that the system causes.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;“In the modern era (until the post-war settlement), the dominant affect was misery. In the nineteenth century, the dominant narrative was that capitalism leads to general enrichment. The public secret of this narrative was the misery of the working class.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;“When misery stopped working as a control strategy, capitalism switched to boredom. In the mid twentieth century, the dominant public narrative was that the standard of living — which widened access to consumption, healthcare and education — was rising. Everyone in the rich countries was happy, and the poor countries were on their way to development. The public secret was that everyone was bored. This was an effect of the Fordist system which was prevalent until the 1980s — a system based on full-time jobs for life, guaranteed welfare, mass consumerism, mass culture, and the co-optation of the labour movement which had been built to fight misery.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Today’s public secret is that everyone is anxious. Anxiety has spread from its previous localised locations (such as sexuality) to the whole of the social field. All forms of intensity, self-expression, emotional connection, immediacy, and enjoyment are now laced with anxiety. It has become the linchpin of subordination.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;“The present dominant affect of anxiety is also known as precarity. Precarity is a type of insecurity which treats people as disposable so as to impose control. Precarity differs from misery in that the necessities of life are not simply absent. They are available, but withheld conditionally.”&lt;/p&gt;

  &lt;p&gt;“Precarity leads to generalised hopelessness; a constant bodily excitation without release. Growing proportions of young people are living at home. Substantial portions of the population — over 10% in the UK — are taking antidepressants. The birth rate is declining, as insecurity makes people reluctant to start families.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Anxiety is personalised in a number of ways — from New Right discourses blaming the poor for poverty, to contemporary therapies which treat anxiety as a neurological imbalance or a dysfunctional thinking style. A hundred varieties of “management” discourse — time management, anger management, parental management, self-branding, gamification — offer anxious subjects an illusion of control in return for ever-greater conformity to the capitalist model of subjectivity.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Then there’s the self-esteem industry, the massive outpouring of media telling people how to achieve success through positive thinking — as if the sources of anxiety and frustration are simply illusory. These are indicative of the tendency to privatise problems, both those relating to work, and those relating to psychology.”&lt;/p&gt;

  &lt;p&gt;“Earlier we argued that people have to be socially isolated in order for a public secret to work. This is true of the current situation, in which authentic communication is increasingly rare. Communication is more pervasive than ever, but increasingly, communication happens only through paths mediated by the system. Hence, in many ways, people are prevented from actually communicating, even while the system demands that everyone be connected and communicable. People both conform to the demand to communicate rather than expressing themselves, and self-censor within mediated spaces.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;“If the first wave provided a machine for fighting misery, and the second wave a machine for fighting boredom, what we now need is a machine for fighting anxiety — and this is something we do not yet have.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Structurally, the system is vulnerable. The reliance on anxiety is a desperate measure, used in the absence of stronger forms of conformity. The system’s attempt to keep running by keeping people feeling powerless leaves it open to sudden ruptures, outbreaks of revolt. So how do we get to the point where we stop feeling powerless?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2025/02/20/misery-boredom-anxiety/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2025/02/19/type-erasure-in-swift/</id>
        <link href="https://www.jessesquires.com/blog/2025/02/19/type-erasure-in-swift/" />
        <title>Type erasure for Equatable and Hashable types in Swift: Lessons from ReactiveCollectionsKit</title>
        <published>2025-02-19T19:29:42-08:00</published>
        <updated>2025-02-19T19:29:42-08:00</updated>

        <category term="software-dev" />
        <category term="series-reactive-collections-kit" /><category term="ios" /><category term="uikit" /><category term="open-source" /><category term="collection-view" /><category term="swift" />
        <summary type="html">&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Type_erasure&quot;&gt;Type erasure&lt;/a&gt; is a method to abstract and encapsulate heterogenous generic types inside a single non-generic concrete type. In programming languages with generic types, this is a mechanism for runtime polymorphism which allows you circumvent the constraints of generics at compile time. In the early days of Swift, generics were more challenging to work with — in particular, protocols with &lt;a href=&quot;https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/#Associated-Types&quot;&gt;associated types&lt;/a&gt;. As of Swift 5.7 (and &lt;a href=&quot;https://github.com/swiftlang/swift-evolution/blob/main/proposals/0309-unlock-existential-types-for-all-protocols.md&quot;&gt;&lt;em&gt;SE-0309: Unlock existentials for all protocols&lt;/em&gt;&lt;/a&gt;), type erasure is now a feature of the Swift compiler. However, there are still situations where you may need to manually write your own type-erased wrappers.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Type_erasure&quot;&gt;Type erasure&lt;/a&gt; is a method to abstract and encapsulate heterogenous generic types inside a single non-generic concrete type. In programming languages with generic types, this is a mechanism for runtime polymorphism which allows you circumvent the constraints of generics at compile time. In the early days of Swift, generics were more challenging to work with — in particular, protocols with &lt;a href=&quot;https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/#Associated-Types&quot;&gt;associated types&lt;/a&gt;. As of Swift 5.7 (and &lt;a href=&quot;https://github.com/swiftlang/swift-evolution/blob/main/proposals/0309-unlock-existential-types-for-all-protocols.md&quot;&gt;&lt;em&gt;SE-0309: Unlock existentials for all protocols&lt;/em&gt;&lt;/a&gt;), type erasure is now a feature of the Swift compiler. However, there are still situations where you may need to manually write your own type-erased wrappers.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;In this post, I’m going to use &lt;a href=&quot;https://github.com/jessesquires/ReactiveCollectionsKit&quot;&gt;ReactiveCollectionsKit&lt;/a&gt; as a case study for some of the challenges you may face with generic programming in Swift and how we can use type erasure to solve them. I’d recommend reading the &lt;a href=&quot;/blog/tags/series-reactive-collections-kit/&quot;&gt;other posts in my series&lt;/a&gt; on ReactiveCollectionsKit.&lt;/p&gt;

&lt;p&gt;The idea behind type erasure is that you need to “erase” the type information for multiple heterogenous types in order to refer to them as a single homogenous type. In other words, type erasure allows you to hide differing underlying types behind a single type.&lt;/p&gt;

&lt;h3 id=&quot;a-brief-overview-and-history-of-type-erasure-in-swift&quot;&gt;A brief overview and history of type erasure in Swift&lt;/h3&gt;

&lt;p&gt;This is a brief and incomplete overview of type erasure in Swift. I will be glossing over many details in order to establish a rudimentary understanding of core concepts for the purposes of this post.&lt;/p&gt;

&lt;p&gt;Before &lt;a href=&quot;https://github.com/swiftlang/swift-evolution/blob/main/proposals/0309-unlock-existential-types-for-all-protocols.md&quot;&gt;SE-0309&lt;/a&gt; (see also: &lt;a href=&quot;https://github.com/swiftlang/swift-evolution/blob/main/proposals/0353-constrained-existential-types.md&quot;&gt;SE-0353&lt;/a&gt; and &lt;a href=&quot;https://github.com/swiftlang/swift-evolution/blob/main/proposals/0346-light-weight-same-type-syntax.md&quot;&gt;SE-0346&lt;/a&gt;), the only option was to write your own type-erased wrapper types. Even if you have never manually implemented type erasure in your own projects, you &lt;em&gt;have&lt;/em&gt; used it. The Swift Standard Library has a number of type-erased types. One of the most common is probably &lt;a href=&quot;https://developer.apple.com/documentation/swift/anyhashable&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyHashable&lt;/code&gt;&lt;/a&gt; because of its use with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dictionary&lt;/code&gt;. There’s also &lt;a href=&quot;https://developer.apple.com/documentation/swift/anycollection&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyCollection&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://developer.apple.com/documentation/swift/anysequence&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnySequence&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://developer.apple.com/documentation/swift/anyindex&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyIndex&lt;/code&gt;&lt;/a&gt;, and many more. If you’re curious how these are implemented, you can view &lt;a href=&quot;https://github.com/swiftlang/swift/tree/main/stdlib&quot;&gt;the Standard Library source code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The convention is to prefix these wrapper types with “Any”. The strategy for implementing them is essentially copying all properties and functions from the generic type into the “Any” type, or simply storing an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Any&lt;/code&gt; property and forwarding all members of the wrapper type to the underlying type.&lt;/p&gt;

&lt;p&gt;Let’s use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyHashable&lt;/code&gt; as a quick example. Below is a simplified implementation to demonstrate core concepts. Note that the &lt;a href=&quot;https://github.com/swiftlang/swift/blob/main/stdlib/public/core/AnyHashable.swift&quot;&gt;actual implementation&lt;/a&gt; is significantly more complex.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyHashable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Hashable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Any&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Hashable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notice that the initializer is generic. It receives a concrete type, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;H&lt;/code&gt;, that conforms to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt;. However, it stores the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;base&lt;/code&gt; property as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Any&lt;/code&gt;, thus &lt;em&gt;erasing&lt;/em&gt; the concrete type. Importantly, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyHashable&lt;/code&gt; also conforms to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt;. The purpose of this is to forward the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt; implementation to the underlying property stored in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;base&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But how do we forward the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt; implementation to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;base&lt;/code&gt; if it is declared as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Any&lt;/code&gt;? If type erasure can be described as “boxing” up a type, then we need to “unbox” the type to transform it back to its concrete type. This process of opening an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Any&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;any&lt;/code&gt; type is known as &lt;a href=&quot;https://en.wikipedia.org/wiki/Reification_(computer_science)&quot;&gt;reification&lt;/a&gt;. In Swift, we do this using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;as?&lt;/code&gt; operator.&lt;/p&gt;

&lt;p&gt;Below is a simplified example of implementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt;, which includes the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash(into:)&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt; functions. These calls are forwarded to the underlying &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;base&lt;/code&gt; property.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyHashable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Hashable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Any&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Hashable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Implement Hashable&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;into&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;hasher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;inout&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Hasher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;base&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;any&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Hashable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;fatalError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;base should be hashable&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;hasher&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;combine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;leftBase&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;any&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Equatable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
              &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;rightBase&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;any&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Equatable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_isEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;lhs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;leftBase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;rhs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rightBase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_isEqual&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Equatable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Equatable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;lhs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;rhs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;rhsAsT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rhs&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as?&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lhs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rhsAsT&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;lhsAsU&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lhs&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as?&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;U&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lhsAsU&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rhs&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Equality is especially tricky. Notice that we have to attempt to cast each side to the same concrete type before continuing with the comparison. This is because the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt; operator operates on two values of the same type. Otherwise, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt; would not make any sense — for example, attempting to compare an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Int&lt;/code&gt; and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; is invalid.&lt;/p&gt;

&lt;p&gt;Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyHashable&lt;/code&gt; allows us to store heterogenous types in a homogenous way. For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt; can only store a collection of values of a single type. The following produces a compiler error:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;3.14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, we could instead store these values as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyHashable&lt;/code&gt;, which works:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;AnyHashable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyHashable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyHashable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;3.14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyHashable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since Swift 5.7 and &lt;a href=&quot;https://github.com/swiftlang/swift-evolution/blob/main/proposals/0309-unlock-existential-types-for-all-protocols.md&quot;&gt;&lt;em&gt;SE-0309: Unlock existentials for all protocols&lt;/em&gt;&lt;/a&gt;, type erasure is now a feature of the Swift compiler. This proposal introduced the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;any&lt;/code&gt; keyword, allowing you to write &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;any MyProtocol&lt;/code&gt; to erase the generic constraints of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MyProtocol&lt;/code&gt;. You no longer need to manually write these type-erased wrappers in most scenarios. Continuing with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyHashable&lt;/code&gt; example, you could instead write &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;any Hashable&lt;/code&gt; and eliminate the need for the custom wrapper type.&lt;/p&gt;

&lt;p&gt;However, while using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;any Hashable&lt;/code&gt; versus &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyHashable&lt;/code&gt; may seem equivalent, there are some important distinctions. Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;any Hashable&lt;/code&gt; gives you an &lt;em&gt;existential&lt;/em&gt; type, an abstract representation, while &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyHashable&lt;/code&gt; is a &lt;em&gt;concrete type&lt;/em&gt;. We can observe the difference in behavior by trying to compare values of each type.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;any&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Hashable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;two&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;any&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Hashable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;one&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;two&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Error: Binary operator &apos;==&apos; cannot be applied to two &apos;any Hashable&apos; operands&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Attempting to compare any two values of type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;any &amp;lt;Some Protocol&amp;gt;&lt;/code&gt; will always produce an error. As mentioned above, this is because the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt; operator compares two values of &lt;em&gt;the same (concrete) type&lt;/em&gt;. At compile time, it is not known what is the actual type of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;any &amp;lt;Some Protocol&amp;gt;&lt;/code&gt;, which is abstract — multiple concrete types could implement the protocol and different types cannot be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Equatable&lt;/code&gt;, for example comparing a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; and an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Int&lt;/code&gt; does not make sense.&lt;/p&gt;

&lt;p&gt;On the other hand, comparison of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyHashable&lt;/code&gt; values works because it &lt;em&gt;is a concrete type&lt;/em&gt;. That is, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt; operator works given two &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyHashable&lt;/code&gt; values.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyHashable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;two&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyHashable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;one&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;two&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// this works, returns false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Doug Gregor &lt;a href=&quot;https://www.douggregor.net/posts/swift-for-cxx-practitioners-type-erasure/&quot;&gt;wrote an excellent post on type erasure&lt;/a&gt; in Swift as part of his &lt;em&gt;“Swift for C++ Practitioners”&lt;/em&gt; series. While it is aimed at explaining Swift to C++ developers, it covers Swift in detail and I highly recommend reading it for a deeper dive. I also recommend reading &lt;a href=&quot;https://swiftrocks.com/whats-any-understanding-type-erasure-in-swift&quot;&gt;this post on type erasure&lt;/a&gt; from Bruno Rocha.&lt;/p&gt;

&lt;h3 id=&quot;generics-and-type-erasure-in-reactivecollectionskit&quot;&gt;Generics and type erasure in ReactiveCollectionsKit&lt;/h3&gt;

&lt;p&gt;Now let’s dive into the details of &lt;a href=&quot;https://github.com/jessesquires/ReactiveCollectionsKit&quot;&gt;ReactiveCollectionsKit&lt;/a&gt; to see a real world example of the challenges presented by generic programming and how we can use type erasure to solve them. If you need a refresher on ReactiveCollectionsKit, you review &lt;a href=&quot;/blog/tags/series-reactive-collections-kit/&quot;&gt;the other posts in this series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The core component in ReactiveCollectionsKit is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt;, which represents a cell in a collection view. Clients can provide any type they want to the library, as long as it conforms to the &lt;a href=&quot;https://jessesquires.github.io/ReactiveCollectionsKit/Protocols/CellViewModel.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt; protocol&lt;/a&gt;. Here’s a simplified definition of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt; for the purposes of this post. (You can find &lt;a href=&quot;https://github.com/jessesquires/ReactiveCollectionsKit/blob/main/Sources/CellViewModel.swift&quot;&gt;the full source on GitHub&lt;/a&gt;.) For folks familiar with the library, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SupplementaryViewModel&lt;/code&gt; follows a similar design.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DiffableViewModel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;associatedtype&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UICollectionViewCell&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;shouldHighlight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;didSelect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DiffableViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Identifiable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Hashable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyHashable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt; inherits from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DiffableViewModel&lt;/code&gt;, which powers how diffing works in the library. Also notice that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt; is generic on the type of cell this view model configures. This is important — it allows clients to mix-and-match the types of cells they display in a collection and provides an ergonomic, type-safe API rather than having to cast from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewCell&lt;/code&gt; to their specific cell subclass.&lt;/p&gt;

&lt;p&gt;Here’s an example concrete implementation of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;PersonCellViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellViewModel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;PersonModel&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// MARK: CellViewModel&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UniqueIdentifier&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;shouldHighlight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;PersonCell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;cell&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// additional configuration&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;didSelect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// handle selection&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All cells in a collection view belong to a section. In ReactiveCollectionsKit, sections are modeled by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SectionViewModel&lt;/code&gt;. This is where the challenges arise from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;associatedtype&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The interface we want for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SectionViewModel&lt;/code&gt; would look something like the following. Again, this definition is simplified for the purposes of this post. Note that sections are also diffable, thus &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SectionViewModel&lt;/code&gt; also inherits from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DiffableViewModel&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SectionViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DiffableViewModel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;cells&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;CellViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Error due to generic constraints via associatedtype&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This does not work, because of the generic constraints in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt;. To solve this, we could make &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SectionViewModel&lt;/code&gt; generic on the type of cell:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SectionViewModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellViewModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DiffableViewModel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;cells&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, this defeats the entire purpose as it restricts a section to displaying a single type of cell. The solution offered by the compiler is to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;any&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SectionViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DiffableViewModel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;cells&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;any&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This &lt;em&gt;almost&lt;/em&gt; works. In many contexts, you could stop here. However, this presents a new problem for ReactiveCollectionsKit. Sections and cells are diffable via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DiffableViewModel&lt;/code&gt;, which inherits from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Equatable&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt;. Without being equatable and hashable, we have no mechanism to perform diffs on cells or sections.&lt;/p&gt;

&lt;p&gt;As mentioned above, values with the type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;any &amp;lt;Some Protocol&amp;gt;&lt;/code&gt; are &lt;em&gt;not&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Equatable&lt;/code&gt; — and thus, &lt;em&gt;not&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt;, which inherits from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Equatable&lt;/code&gt;. The problem here is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;any CellViewModel&lt;/code&gt; is not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Equatable&lt;/code&gt;. This is why we need to introduce a new type-erased wrapper for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt;. We’ll call it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyCellViewModel&lt;/code&gt;. This results in the following interface for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SectionViewModel&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SectionViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DiffableViewModel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;cells&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;AnyCellViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s write an initial implementation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyCellViewModel&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyCellViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellViewModel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// MARK: DiffableViewModel&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyHashable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_id&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// MARK: CellViewModel&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;typealias&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UICollectionViewCell&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;shouldHighlight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_shouldHighlight&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UICollectionViewCell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;_configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;didSelect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;_didSelect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coordinator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// MARK: Private&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyHashable&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_shouldHighlight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;CellType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Void&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_didSelect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Void&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// MARK: Init&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellViewModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_shouldHighlight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shouldHighlight&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_configure&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as!&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;CellType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_didSelect&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;didSelect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The strategy here is to copy all properties and methods from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt; protocol. Properties are copied directly and methods are copied as closure properties. Next, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyCellViewModel&lt;/code&gt; implements the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt; protocol and forwards all implementations to the copied properties. Notice we have to force-cast the cell type in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_configure()&lt;/code&gt; closure from the generic &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewCell&lt;/code&gt; from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyCellViewModel&lt;/code&gt; to the specific cell subclass provided by the concrete &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt; we receive in the initializer. This might seem dangerous, but we know this is a safe operation.&lt;/p&gt;

&lt;p&gt;This works as far as it satisfies the type system. However, there is more to do. The above definition of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyCellViewModel&lt;/code&gt; will not work for diffing. We need to implement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Equatable&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt;, and we need more information from the original &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viewModel&lt;/code&gt; instance to do it. So far, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyCellViewModel&lt;/code&gt; only knows about the members of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt;. Yet, clients can provide any type that conforms to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt; to the library. Consider the example above using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PersonCellViewModel&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;PersonCellViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellViewModel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;PersonModel&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// ... implementation continues ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this situation, being &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Equatable&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt; involves comparing the value stored in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let person: PersonModel&lt;/code&gt;. We cannot store a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PersonModel&lt;/code&gt; property on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyCellViewModel&lt;/code&gt; — that does not generalize. We also cannot make &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyCellViewModel&lt;/code&gt; generic, which defeats the entire purpose of type erasure. What we need to do is store the entire &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PersonCellViewModel&lt;/code&gt; inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyCellViewModel&lt;/code&gt; in a way that is generalized (though not using generics), but also allows us access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Equatable&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt;. That is, we cannot simply use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Any&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What we can do is store the concrete view model as an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyHashable&lt;/code&gt; value:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyCellViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellViewModel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ... implementation continues ...&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_viewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyHashable&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellViewModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_viewModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// ... implementation continues ...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This erases the generic constraints of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viewModel&lt;/code&gt; imposed by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt; while preserving access to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Equatable&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt;. Now, we can simply forward the implementations from these two protocols:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyCellViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Equatable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyCellViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyCellViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_viewModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_viewModel&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyCellViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Hashable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;into&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;hasher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;inout&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Hasher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_viewModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;into&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hasher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This resolves all of our issues. Clients receive an ergonomic, type-safe, generic API for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt;. The library can successfully type erase all the values provided as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyCellViewModel&lt;/code&gt;. And diffing &lt;em&gt;just works&lt;/em&gt; in any and every scenario using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Equatable&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can provide a convenience method to facilitate type erasure:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellViewModel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;eraseToAnyViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyCellViewModel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// prevent &quot;double erasure&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;erasedViewModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as?&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyCellViewModel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;erasedViewModel&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyCellViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Additionally, you’ll notice that &lt;a href=&quot;https://jessesquires.github.io/ReactiveCollectionsKit/Structs/SectionViewModel.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SectionViewModel&lt;/code&gt;&lt;/a&gt; provides a number of convenience initializers using generics. In scenarios where you &lt;em&gt;do not have mixed data types&lt;/em&gt;, the generic initializers allow you ignore this implementation detail and the library handles the type erasure for you. Below is a simplified example of one convenience initializer for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SectionViewModel&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SectionViewModel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellViewModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cells&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cells&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cells&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;eraseToAnyViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This initializer receives a single concrete type, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cell&lt;/code&gt;, that conforms to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt;. It performs the type erasure internally. This greatly improves the usability of the API and helps reduce the burden on clients of having to always convert to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyCellViewModel&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;While generic programming is a powerful tool, there are often times where you may find yourself fighting with the type system. If your solution requires the use of generics but you are struggling to reconcile heterogenous types with generic constraints, a great tool to reach for is type erasure via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;any&lt;/code&gt;. If that does not work, like in the case of ReactiveCollectionsKit, you can write your own type-erased wrapper type. Importantly, if you need to preserve conformance to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Equatable&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt;, you can utilize &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyHashable&lt;/code&gt; as demonstrated above.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2025/02/19/type-erasure-in-swift/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/12/19/diffable-data-source-main-actor-inconsistency/</id>
        <link href="https://www.jessesquires.com/blog/2024/12/19/diffable-data-source-main-actor-inconsistency/" />
        <title>UIKit DiffableDataSource API inconsistencies with Swift Concurrency annotations explained</title>
        <published>2024-12-19T11:54:07-08:00</published>
        <updated>2024-12-19T11:54:07-08:00</updated>

        <category term="software-dev" />
        <category term="xcode" /><category term="ios" /><category term="uikit" /><category term="swift" /><category term="concurrency" />
        <summary type="html">&lt;p&gt;UIKit provides two diffable data source APIs, one for &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource-9tqpa&quot;&gt;collections&lt;/a&gt; and one for &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uitableviewdiffabledatasource-2euir&quot;&gt;tables&lt;/a&gt;.
Recently, while working on &lt;a href=&quot;/blog/2024/10/18/introducing-reactivecollectionskit/&quot;&gt;ReactiveCollectionKit&lt;/a&gt;, I noticed that the APIs were updated for Swift Concurrency in the iOS 18 SDK, but the annotations were inconsistent with the documentation.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;UIKit provides two diffable data source APIs, one for &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource-9tqpa&quot;&gt;collections&lt;/a&gt; and one for &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uitableviewdiffabledatasource-2euir&quot;&gt;tables&lt;/a&gt;.
Recently, while working on &lt;a href=&quot;/blog/2024/10/18/introducing-reactivecollectionskit/&quot;&gt;ReactiveCollectionKit&lt;/a&gt;, I noticed that the APIs were updated for Swift Concurrency in the iOS 18 SDK, but the annotations were inconsistent with the documentation.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;The main entry point into working with snapshots is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apply(_:animatingDifferences:completion:)&lt;/code&gt; method. There’s one for &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource-9tqpa/apply(_:animatingdifferences:completion:)&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewDiffableDataSource&lt;/code&gt;&lt;/a&gt; and one for &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uitableviewdiffabledatasource-2euir/apply(_:animatingdifferences:completion:)&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UITableViewDiffableDataSource&lt;/code&gt;&lt;/a&gt;. The method signatures and docs are identical for both. They were both updated with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@MainActor&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@preconcurrency&lt;/code&gt; annotations for iOS 18.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;@MainActor&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;@preconcurrency&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSDiffableDataSourceSnapshot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;SectionIdentifierType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ItemIdentifierType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;animatingDifferences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, the documentation states:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;You can safely call this method from a background queue, but you must do so consistently in your app. Always call this method exclusively from the main queue or from a background queue.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a problem if you were calling these methods from a background queue and attempted to upgrade to Swift 6. This is because Swift 6 Concurrency makes calling this method from a background queue impossible. The compiler will not let you do it because it is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@MainActor&lt;/code&gt; rather than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nonisolated&lt;/code&gt;. So… what’s the deal?&lt;/p&gt;

&lt;p&gt;I reached out to &lt;a href=&quot;https://mas.to/@smileyborg/&quot;&gt;Tyler Fox&lt;/a&gt; from the UIKit team on Mastodon to ask if this was a mistake. As it turns out, it is not a mistake and his reply was incredibly helpful and insightful. For posterity and documentation purposes (and because social media is ephemeral and unreliable), I’m going to reproduce &lt;a href=&quot;https://mas.to/@smileyborg/113427085770601417&quot;&gt;his entire response here&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The main actor annotation is intentional, see the full explanation attached. There isn’t a perfect solution here though, unfortunately.&lt;/p&gt;

  &lt;p&gt;Also be sure to refer &lt;a href=&quot;https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/updating_collection_views_using_diffable_data_sources&quot;&gt;to this documentation&lt;/a&gt; for additional best practices around diffable data source, especially around using proper identifiers (instead of lightweight data structures) as this is key to efficient performance with large data sets.&lt;/p&gt;

  &lt;p&gt;This was an intentional change made to the diffable data source API in the iOS 18 SDK. The existing diffable data source API and implementation has strict concurrency requirements that do not translate into Swift Concurrency (specifically, it must be used from a single dispatch queue, which nonisolated cannot express).&lt;/p&gt;

  &lt;p&gt;We have seen a number of issues stemming from usage of diffable data source on background queues/threads, and the performance benefits of doing this are generally minimal due to the fact that only the diffing of identifiers in the old &amp;amp; new snapshots happens on the background queue/thread; the work to set up and execute the Ul updates and animations for cells always happens on the main thread. Therefore, we made the decision to restrict diffable data source to the main actor when using Swift Concurrency, as this ensures correctness in all cases and is nearly always the best approach anyways. If you were previously applying snapshots from a background queue, we recommend you update your implementation to do so on the main queue instead.&lt;/p&gt;

  &lt;p&gt;If you are concerned about performance, you should measure and profile your app with large data sets using Instruments (e.g. Time Profiler). You will almost certainly find that the diffing portion of the work (which was the only portion eligible to occur off the main thread) is negligible compared to the work involved in creating cells, measuring their sizes, performing layout, etc as part of Ul updates. If you do see any significant work happening during the diffing process, make sure you are using proper identifiers, and revisit the hashing and equality implementations of your identifiers. If your snapshots contain tens or hundreds of thousands of items (or more), you may wish to use techniques such as pagination to reduce the total number of items populated in the Ul at once.&lt;/p&gt;

  &lt;p&gt;Finally, if you do discover a use case where you believe background queue diffing is essential, please do submit feedback so we can understand the use case and make recommendations or consider potential enhancements to the AP to better support it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you were running into issues with these APIs or were confused by the discrepancy in the docs, I hope this helps! The inconsistency remains in the written documentation, which is not ideal. But, I’m very grateful that Tyler took the time to reply and clear up the confusion. Thanks Tyler!&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/12/19/diffable-data-source-main-actor-inconsistency/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/10/23/reactivecollectionskit-diffing/</id>
        <link href="https://www.jessesquires.com/blog/2024/10/23/reactivecollectionskit-diffing/" />
        <title>Diffing in ReactiveCollectionsKit: Understanding Identity and Equality</title>
        <published>2024-10-23T12:43:45-07:00</published>
        <updated>2024-10-23T12:43:45-07:00</updated>

        <category term="software-dev" />
        <category term="series-reactive-collections-kit" /><category term="ios" /><category term="uikit" /><category term="swiftui" /><category term="open-source" /><category term="collection-view" />
        <summary type="html">&lt;p&gt;Welcome to the next post in &lt;a href=&quot;/blog/tags/series-reactive-collections-kit/&quot;&gt;my series about ReactiveCollectionsKit&lt;/a&gt;. Today I want to discuss diffing. Understanding diffing requires understanding two core concepts: &lt;strong&gt;identity&lt;/strong&gt; and &lt;strong&gt;equality&lt;/strong&gt;. These are two ideas that are also relevant and applicable to programming in general, and can often be confusing for newcomers.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;Welcome to the next post in &lt;a href=&quot;/blog/tags/series-reactive-collections-kit/&quot;&gt;my series about ReactiveCollectionsKit&lt;/a&gt;. Today I want to discuss diffing. Understanding diffing requires understanding two core concepts: &lt;strong&gt;identity&lt;/strong&gt; and &lt;strong&gt;equality&lt;/strong&gt;. These are two ideas that are also relevant and applicable to programming in general, and can often be confusing for newcomers.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;In &lt;a href=&quot;https://github.com/jessesquires/ReactiveCollectionsKit&quot;&gt;ReactiveCollectionsKit&lt;/a&gt;, diffing is modeled by the &lt;a href=&quot;https://jessesquires.github.io/ReactiveCollectionsKit/Protocols/DiffableViewModel.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DiffableViewModel&lt;/code&gt;&lt;/a&gt; protocol, which encapsulates the definitions of both identity and equality.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;typealias&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UniqueIdentifier&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyHashable&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DiffableViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Identifiable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Hashable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Sendable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UniqueIdentifier&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All view model types in the library inherit from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DiffableViewModel&lt;/code&gt; — this includes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SupplementaryViewModel&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SectionViewModel&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CollectionViewModel&lt;/code&gt;. As you can see, diffability is composable. Cells and supplementary views make up a section, while sections make up a collection. Each level of your model can be diffed.&lt;/p&gt;

&lt;p&gt;This protocol builds upon the Swift Standard Library. It requires you to provide a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; property, which can be &lt;em&gt;anything&lt;/em&gt; that is hashable. This property is inherited from the &lt;a href=&quot;https://developer.apple.com/documentation/swift/identifiable&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Identifiable&lt;/code&gt;&lt;/a&gt; protocol, which is generic. Thus, ReactiveCollectionsKit refines the definition to enforce that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; is an instance of &lt;a href=&quot;https://developer.apple.com/documentation/swift/anyhashable&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyHashable&lt;/code&gt;&lt;/a&gt;. Most commonly, the identifier is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;, but it could be an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Int&lt;/code&gt; or any custom type. In fact, you could return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; for this property in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt; definition.&lt;/p&gt;

&lt;p&gt;The protocol also inherits from &lt;a href=&quot;https://developer.apple.com/documentation/Swift/Hashable&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt;&lt;/a&gt;, which inherits from &lt;a href=&quot;https://developer.apple.com/documentation/swift/equatable&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Equatable&lt;/code&gt;&lt;/a&gt;. This enforces that user-defined view model instances are both hashable and equatable. In Swift, the compiler can synthesize these protocol requirements for you if all properties of a type are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt; as well. This helps remove a lot of boilerplate in ReactiveCollectionsKit when defining your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt; types.&lt;/p&gt;

&lt;p&gt;Altogether, this means diffing requires three pieces of information in order to work correctly, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash(into:)&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Identifiable&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UniqueIdentifier&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Hashable&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;into&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;hasher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;inout&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Hasher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Equatable&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;identity&quot;&gt;Identity&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Identity&lt;/em&gt; concerns itself with &lt;strong&gt;permanently&lt;/strong&gt; and &lt;strong&gt;uniquely&lt;/strong&gt; identifying a single instance of an object. An identity &lt;em&gt;never&lt;/em&gt; changes. Identity answers the question &lt;em&gt;“who is this?”&lt;/em&gt; For example, a passport encapsulates the concept of &lt;em&gt;identity&lt;/em&gt; for a person. A passport permanently and uniquely identifies and corresponds to a single person. (For the sake of this example, let’s pretend that people cannot have multiple passports.) Identity is captured by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Identifiable&lt;/code&gt; protocol and the corresponding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; property.&lt;/p&gt;

&lt;h3 id=&quot;equality&quot;&gt;Equality&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Equality&lt;/em&gt; concerns itself with &lt;strong&gt;ephemeral&lt;/strong&gt; &lt;em&gt;traits&lt;/em&gt; or &lt;em&gt;properties&lt;/em&gt; of a single unique object that &lt;em&gt;change&lt;/em&gt; over time. Equality answers the question &lt;em&gt;“which of these objects with this specific &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; is the most up-to-date?”&lt;/em&gt; For example, a person is a unique entity, but they can change their hairstyle, they can wear different clothes, and can generally change any aspect of their physical appearance. While we can uniquely identify a person using their passport on any day, their physical appearance changes day-to-day or year-to-year. Equality is captured by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt; (and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Equatable&lt;/code&gt;) protocol and the corresponding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash(into:)&lt;/code&gt; functions.&lt;/p&gt;

&lt;h3 id=&quot;becoming-diffable&quot;&gt;Becoming diffable&lt;/h3&gt;

&lt;p&gt;Using this example, consider constructing a list of people to display in a collection. We can uniquely identify each person (using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;) in the collection. This allows us to determine (1) if they are present, (2) their precise position, (3) if they have been inserted, deleted, or moved. Next, we can determine if they have changed (using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt;) since we last saw them. This allows us to determine when a unique person in the collection needs to be reloaded or reconfigured.&lt;/p&gt;

&lt;p&gt;In other words, &lt;em&gt;identity&lt;/em&gt; concerns itself with the &lt;em&gt;structure&lt;/em&gt; of the collection — &lt;em&gt;where is each item located (or not)?&lt;/em&gt; On the other hand, &lt;em&gt;equality&lt;/em&gt; concerns itself with the individual &lt;em&gt;state&lt;/em&gt; of each item — &lt;em&gt;is that item up-to-date?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Both &lt;a href=&quot;https://github.com/instagram/iglistkit&quot;&gt;IGListKit&lt;/a&gt; and &lt;a href=&quot;https://github.com/plangrid/reactivelists&quot;&gt;ReactiveLists&lt;/a&gt; got this correct, but their implementations are more cumbersome and manual. ReactiveCollectionsKit improves upon both of these implementations with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DiffableViewModel&lt;/code&gt; protocol above, and by harnessing Swift’s type system. Identifiers can be &lt;em&gt;anything&lt;/em&gt; that is hashable and because Swift can automatically synthesize conformances to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Equatable&lt;/code&gt;, clients can get all of that functionality for free.&lt;/p&gt;

&lt;p&gt;If you need to optimize your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Equatable&lt;/code&gt; implementations, you can manually implement the protocols in your view models. The primary motivation for overriding your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hashable&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Equatable&lt;/code&gt; implementations is &lt;strong&gt;performance&lt;/strong&gt;. You want your equality comparisons for diffing to be fast. This also includes your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; property from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DiffableViewModel&lt;/code&gt;. These three definitions — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;,  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash(into:)&lt;/code&gt; — will be the main bottleneck for performance when it comes to diffing. Make sure they are fast.&lt;/p&gt;

&lt;h3 id=&quot;shortcomings-in-uikit&quot;&gt;Shortcomings in UIKit&lt;/h3&gt;

&lt;p&gt;Most importantly, this brings me to why a library like ReactiveCollectionsKit is necessary.&lt;/p&gt;

&lt;p&gt;The collection view diffing APIs in UIKit &lt;strong&gt;do not handle &lt;em&gt;equality&lt;/em&gt;&lt;/strong&gt;. &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewDiffableDataSource&lt;/code&gt;&lt;/a&gt; &lt;strong&gt;only concerns itself with &lt;em&gt;identity&lt;/em&gt;&lt;/strong&gt;. That is, it handles the structure (inserts/deletes/moves) for you, but you must handle reload (or reconfigure) for item property changes. (See: &lt;a href=&quot;https://mobile.twitter.com/smileyborg/status/1402164265897758720&quot;&gt;Tyler Fox&lt;/a&gt;.) In my opinion, UIKit got this &lt;em&gt;wrong&lt;/em&gt;. The diffing APIs should have also handled &lt;em&gt;equality&lt;/em&gt;, like IGListKit and ReactiveLists.&lt;/p&gt;

&lt;p&gt;If you use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewDiffableDataSource&lt;/code&gt; out-of-the-box and do not check for item equality and reconfigure those items, then the data in your collection or list &lt;strong&gt;will not update&lt;/strong&gt;. The &lt;em&gt;structure&lt;/em&gt; of your collection will update — that is, items will be correctly inserted, deleted, or moved, but an item will not refresh if it has been updated in-place.&lt;/p&gt;

&lt;p&gt;Correctly checking &lt;em&gt;equality&lt;/em&gt; for cells, supplementary views, and sections in order to reload or reconfigure them accordingly is not a trivial task. You can &lt;a href=&quot;https://github.com/jessesquires/ReactiveCollectionsKit/blob/main/Sources/DiffableDataSource.swift&quot;&gt;find the source code here&lt;/a&gt;. I’ve annotated it heavily with comments to explain what is happening and why.&lt;/p&gt;

&lt;p&gt;This is one of the primary motivations for this library. It does not merely wrap the underlying diffing APIs and reduce boilerplate, it provides critical functionality for making lists and collections work &lt;em&gt;fully&lt;/em&gt; and &lt;em&gt;correctly&lt;/em&gt;. When using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewDiffableDataSource&lt;/code&gt;, you must track property changes for all items in the collection on your own, and then reload or reconfigure those items accordingly. When using ReactiveCollectionsKit, all of this work is handled automatically for you.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/10/23/reactivecollectionskit-diffing/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/10/18/introducing-reactivecollectionskit/</id>
        <link href="https://www.jessesquires.com/blog/2024/10/18/introducing-reactivecollectionskit/" />
        <title>Introducing ReactiveCollectionsKit: A Swift replacement for IGListKit</title>
        <published>2024-10-18T12:30:18-07:00</published>
        <updated>2024-10-18T12:30:18-07:00</updated>

        <category term="software-dev" />
        <category term="series-reactive-collections-kit" /><category term="ios" /><category term="uikit" /><category term="swiftui" /><category term="open-source" /><category term="collection-view" />
        <summary type="html">&lt;p&gt;I recently released a new open source project called &lt;a href=&quot;https://github.com/jessesquires/ReactiveCollectionsKit&quot;&gt;ReactiveCollectionsKit&lt;/a&gt;. It is a modern, fast, and flexible library for building data-driven, declarative, reactive, and diffable collections and lists for iOS. This library is the culmination of everything I learned from building and maintaining &lt;a href=&quot;https://github.com/instagram/iglistkit&quot;&gt;IGListKit&lt;/a&gt;, &lt;a href=&quot;https://github.com/plangrid/reactivelists&quot;&gt;ReactiveLists&lt;/a&gt;, and &lt;a href=&quot;https://github.com/jessesquires/JSQDataSourcesKit&quot;&gt;JSQDataSourcesKit&lt;/a&gt;. The 4th time’s a charm! 😅  🍀 I truly hope this is the last &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionView&lt;/code&gt; library I ever write and maintain. I think it will be. You can find &lt;a href=&quot;https://jessesquires.github.io/ReactiveCollectionsKit/&quot;&gt;the documentation here&lt;/a&gt;.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;I recently released a new open source project called &lt;a href=&quot;https://github.com/jessesquires/ReactiveCollectionsKit&quot;&gt;ReactiveCollectionsKit&lt;/a&gt;. It is a modern, fast, and flexible library for building data-driven, declarative, reactive, and diffable collections and lists for iOS. This library is the culmination of everything I learned from building and maintaining &lt;a href=&quot;https://github.com/instagram/iglistkit&quot;&gt;IGListKit&lt;/a&gt;, &lt;a href=&quot;https://github.com/plangrid/reactivelists&quot;&gt;ReactiveLists&lt;/a&gt;, and &lt;a href=&quot;https://github.com/jessesquires/JSQDataSourcesKit&quot;&gt;JSQDataSourcesKit&lt;/a&gt;. The 4th time’s a charm! 😅  🍀 I truly hope this is the last &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionView&lt;/code&gt; library I ever write and maintain. I think it will be. You can find &lt;a href=&quot;https://jessesquires.github.io/ReactiveCollectionsKit/&quot;&gt;the documentation here&lt;/a&gt;.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;h3 id=&quot;a-very-brief-history&quot;&gt;A very brief history&lt;/h3&gt;

&lt;p&gt;I started doing early prototyping for this library a few years ago, but then other projects (and life in general) took priority. I stopped working on it for a while. I finally came back to the project earlier this year and did a “soft” &lt;a href=&quot;https://github.com/jessesquires/ReactiveCollectionsKit/releases/tag/0.1.0&quot;&gt;initial release&lt;/a&gt; a few months ago. Since then, I’ve been steadily &lt;a href=&quot;https://github.com/jessesquires/ReactiveCollectionsKit/releases&quot;&gt;working on improvements&lt;/a&gt; and new features. I’ve also started to get some interest in the project and there have been a &lt;a href=&quot;https://github.com/jessesquires/ReactiveCollectionsKit/graphs/contributors&quot;&gt;few contributors&lt;/a&gt; helping out and making significant contributions. Notably, this library is now being used in &lt;a href=&quot;https://www.duolingo.com&quot;&gt;Duolingo&lt;/a&gt;, which I helped initiate and integrate.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/jessesquires/ReactiveCollectionsKit&quot;&gt;ReactiveCollectionsKit&lt;/a&gt; contains a number of improvements, optimizations, and refinements over the aforementioned libraries — &lt;a href=&quot;https://github.com/instagram/iglistkit&quot;&gt;IGListKit&lt;/a&gt;, &lt;a href=&quot;https://github.com/plangrid/reactivelists&quot;&gt;ReactiveLists&lt;/a&gt;, and &lt;a href=&quot;https://github.com/jessesquires/JSQDataSourcesKit&quot;&gt;JSQDataSourcesKit&lt;/a&gt;. I have incorporated what I think are the best ideas and architecture design elements from each of these libraries, while eliminating or improving upon the shortcomings. Importantly, this library uses modern &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionview&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionView&lt;/code&gt;&lt;/a&gt; APIs (like &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewDiffableDataSource&lt;/code&gt;&lt;/a&gt;), which were unavailable when the previous libraries were written.&lt;/p&gt;

&lt;p&gt;ReactiveCollectionsKit has no third-party dependencies and is written in Swift.&lt;/p&gt;

&lt;h3 id=&quot;what-about-swiftui&quot;&gt;What about SwiftUI?&lt;/h3&gt;

&lt;p&gt;Of course, the obvious question is: &lt;em&gt;isn’t SwiftUI the future?&lt;/em&gt; It probably &lt;em&gt;will be&lt;/em&gt; — eventually. But the difficult truth is that &lt;a href=&quot;https://github.com/jessesquires/TIL/blob/main/apple_platform/swiftui.md#known-issues--workarounds&quot;&gt;it simply isn’t right now&lt;/a&gt;. Why build another UIKit-based library? SwiftUI performance is still a significant issue, not to mention all the bugs, missing APIs, and lack of back-porting new APIs to older OS versions. SwiftUI works best when you only use the latest SDKs and target the latest operating systems. Yet, most of us must support older versions of iOS. Even then, for any sufficiently advanced app SwiftUI alone will not suffice.&lt;/p&gt;

&lt;p&gt;SwiftUI still does not provide a proper &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionView&lt;/code&gt; replacement. Yes, &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/grid&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Grid&lt;/code&gt;&lt;/a&gt; exists but it is nowhere close to a replacement for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionView&lt;/code&gt; and the power of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewLayout&lt;/code&gt;. While SwiftUI’s &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/list&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt;&lt;/a&gt; is pretty good much of the time, performance can still suffer. Also, both &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/LazyVStack&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LazyVStack&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/LazyHStack&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LazyHStack&lt;/code&gt;&lt;/a&gt; suffer from severe performance issues when you have large amounts of data.&lt;/p&gt;

&lt;p&gt;Luckily, SwiftUI provides solid APIs for &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/uikit-integration&quot;&gt;integration with UIKit&lt;/a&gt; so you can easily use ReactiveCollectionsKit in a SwiftUI-based view or app.&lt;/p&gt;

&lt;h3 id=&quot;reactivecollectionskit-overview&quot;&gt;ReactiveCollectionsKit overview&lt;/h3&gt;

&lt;p&gt;The general idea behind ReactiveCollectionsKit is about declaratively defining your collection or list using an &lt;a href=&quot;https://en.wikipedia.org/wiki/Model–view–viewmodel&quot;&gt;MVVM-style&lt;/a&gt; separation of concerns. (This was largely inspired by ReactiveLists.) The library does not care what your data models are, you are only responsible for mapping your model objects to the view models that you then provide to the library.&lt;/p&gt;

&lt;p&gt;Here’s a brief example of building a list from an array of data models.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// array of some data models&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;models&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;MyModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// create cell view models from the data&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;cells&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;models&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyCellViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// create the sections with cells&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;section&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SectionViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;my_section&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;cells&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cells&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// create the collection with sections&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;collection&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CollectionViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;sections&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;section&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// initialize the driver with the view model and view&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;listLayout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UICollectionViewCompositionalLayout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;using&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;appearance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grouped&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;collectionView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UICollectionView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;collectionViewLayout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;listLayout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;driver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CollectionViewDriver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;collectionView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The collection view is updated and animated automatically. When your data changes, simply regenerate your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CollectionViewModel&lt;/code&gt; (like above) and send this to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CollectionViewDriver&lt;/code&gt; instance.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// when the models change, generate a new view model (like above)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;updated&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CollectionViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;sections&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;section1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;section2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;updated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There is also an extensive &lt;a href=&quot;https://github.com/jessesquires/ReactiveCollectionsKit/tree/main/Example&quot;&gt;example project&lt;/a&gt; included in the repo on GitHub.&lt;/p&gt;

&lt;h3 id=&quot;architecture-and-design&quot;&gt;Architecture and design&lt;/h3&gt;

&lt;p&gt;I want to share some high-level notes on the architecture and core concepts in &lt;a href=&quot;https://github.com/jessesquires/ReactiveCollectionsKit&quot;&gt;ReactiveCollectionsKit&lt;/a&gt;, along with comparisons to the other libraries I have worked on — &lt;a href=&quot;https://github.com/instagram/iglistkit&quot;&gt;IGListKit&lt;/a&gt;, &lt;a href=&quot;https://github.com/plangrid/reactivelists&quot;&gt;ReactiveLists&lt;/a&gt;, and &lt;a href=&quot;https://github.com/jessesquires/JSQDataSourcesKit&quot;&gt;JSQDataSourcesKit&lt;/a&gt;. This section assumes some familiarity with all four libraries. However, even if you have never used them, it is still possible to follow along.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://jessesquires.github.io/ReactiveCollectionsKit/Protocols/CellViewModel.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt;&lt;/a&gt; is the fundamental or “atomic” component in the library. It encapsulates all data, configuration, interaction, and view registration for a single cell. This is similar to ReactiveLists. In IGListKit, this component corresponds to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IGListSectionController&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For brevity and clarity, here’s a simplified definition of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellViewModel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;associatedtype&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UICollectionViewCell&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UniqueIdentifier&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;registration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ViewRegistration&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;didSelect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;coordinator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CellEventCoordinator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href=&quot;https://jessesquires.github.io/ReactiveCollectionsKit/Structs/CollectionViewModel.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CollectionViewModel&lt;/code&gt;&lt;/a&gt; defines the entire structure of the collection. It is an immutable representation of your collection of models, which can be anything. It is composed of &lt;a href=&quot;https://jessesquires.github.io/ReactiveCollectionsKit/Structs/SectionViewModel.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SectionViewModel&lt;/code&gt;&lt;/a&gt; objects that define sections, which are composed of the aforementioned &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt; objects. A section also defines headers, footers, and supplementary views.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://jessesquires.github.io/ReactiveCollectionsKit/Classes/CollectionViewDriver.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CollectionViewDriver&lt;/code&gt;&lt;/a&gt; is the primary entry point into the library. This component owns the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CollectionViewModel&lt;/code&gt;, is responsible for diffing and applying updates, and manages the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionView&lt;/code&gt;. The “driver” terminology is borrowed from ReactiveLists. This component is more or less equivalent to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IGListAdapter&lt;/code&gt; found in IGListKit.&lt;/p&gt;

&lt;p&gt;Together, these core components allow for uni-directional data flow. The general workflow is:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Fetch or update your data models&lt;/li&gt;
  &lt;li&gt;From those models, generate your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt; objects&lt;/li&gt;
  &lt;li&gt;Construct your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SectionViewModel&lt;/code&gt; objects and the final &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CollectionViewModel&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Send the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CollectionViewModel&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CollectionViewDriver&lt;/code&gt;, which will then perform a diff and update the view&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;review-of-prior-art&quot;&gt;Review of prior art&lt;/h3&gt;

&lt;p&gt;Here I want to address some of the main the pros and cons of &lt;a href=&quot;https://github.com/instagram/iglistkit&quot;&gt;IGListKit&lt;/a&gt;, &lt;a href=&quot;https://github.com/plangrid/reactivelists&quot;&gt;ReactiveLists&lt;/a&gt;, and &lt;a href=&quot;https://github.com/jessesquires/JSQDataSourcesKit&quot;&gt;JSQDataSourcesKit&lt;/a&gt;. Again, this section assumes some familiarity with all four libraries. But, it should still be possible to follow along.&lt;/p&gt;

&lt;h4 id=&quot;iglistkit&quot;&gt;IGListKit&lt;/h4&gt;

&lt;p&gt;The main shortcomings of &lt;a href=&quot;https://github.com/instagram/iglistkit&quot;&gt;IGListKit&lt;/a&gt; are the lack of expressivity in Objective-C’s type system, some undesirable boilerplate set up, mutability, and using sections as the base or fundamental component of a list or collection. The library has added annotations to make interoperability with Swift significantly better, but it is not quite the same as a native Swift API. The extra boilerplate involved is also largely because of the nature of Objective-C, which is simply more verbose.&lt;/p&gt;

&lt;p&gt;While IGListKit is general-purpose, much of the design is informed by what we needed specifically at Instagram. It is centered around the concept of lists (like a News Feed) rather than grids or collections, for obvious reasons. A shortcoming of IGListKit is that the “atomic” component is an entire section of multiple items. The section-based design was informed by how lists were architected in the Instagram feed, where each post was its own section. A section in IGListKit &lt;em&gt;could&lt;/em&gt; have a single item and in this scenario it more closely resembles a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CellViewModel&lt;/code&gt; in ReactiveCollectionsKit.&lt;/p&gt;

&lt;p&gt;What IGListKit got right was diffing — in fact, we pioneered that entire idea. (H/T &lt;a href=&quot;https://www.threads.net/@_rnystrom&quot;&gt;Ryan Nystrom&lt;/a&gt;). The diffing APIs in UIKit &lt;em&gt;came after&lt;/em&gt; we released IGListKit and were heavily influenced by what we accomplished. IGListKit also manually implemented and supported many advanced layout features that are now provided out-of-the-box by &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewcompositionallayout&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewCompositionalLayout&lt;/code&gt;&lt;/a&gt;. IGListKit’s section-based design was also largely a product of the limitations of the existing layout APIs. All we had back then was &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewlayout&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewFlowLayout&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;IGListKit is very imperative and mutable. After you hook-up your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IGListAdapter&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IGListSectionController&lt;/code&gt; objects, you update sections in-place. IGListKit encourages immutable data models but this is not enforceable in Objective-C, nor is it enforced in the API. IGListKit &lt;em&gt;does&lt;/em&gt; have uni-directional data flow in some sense, but you provide your data imperatively via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IGListAdapterDataSource&lt;/code&gt; which also requires you to manually manage a mapping of your data model objects to their corresponding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IGListSectionController&lt;/code&gt; objects. With this section-based approach, clients are also responsible for manually reporting the number of items in the section.&lt;/p&gt;

&lt;p&gt;ReactiveCollectionsKit removes all the boilerplate required by IGListKit. For example, determining the number of items in a section is derived automatically from the structure of the data itself.&lt;/p&gt;

&lt;h4 id=&quot;reactivelists&quot;&gt;ReactiveLists&lt;/h4&gt;

&lt;p&gt;The main shortcomings of &lt;a href=&quot;https://github.com/plangrid/reactivelists&quot;&gt;ReactiveLists&lt;/a&gt; are that it uses older UIKit APIs and a custom, third-party diffing library. It maintains entirely separate infrastructure for tables and collections, which duplicates a lot of functionality. There’s a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TableViewModel&lt;/code&gt; for constructing table views and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CollectionViewModel&lt;/code&gt; for constructing collection views, which use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UITableView&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionView&lt;/code&gt; under-the-hood, respectively. This is because ReactiveLists pre-dates the modern collection view APIs for &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource&quot;&gt;diffing&lt;/a&gt; and making &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewcompositionallayout/3600951-list&quot;&gt;list layouts&lt;/a&gt; using collection view. ReactiveLists is also a bit incomplete as we only implemented the APIs we needed at PlanGrid.&lt;/p&gt;

&lt;p&gt;What ReactiveLists got right was a declarative API, defining an immutable model of your list or collection, using an individual cell as the base or fundamental component, and uni-directional data flow. With ReactiveLists, you declaratively define your entire collection view model and regenerate it whenever your underlying data model changes. ReactiveCollectionsKit borrowed this directly and improved upon it. Namely, ReactiveCollectionsKit uses generics to allow you to specify the exact type of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewCell&lt;/code&gt; that your view model configures instead of having to cast (using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;as!&lt;/code&gt;) to your specific cell subclass.&lt;/p&gt;

&lt;h4 id=&quot;jsqdatasourceskit&quot;&gt;JSQDataSourcesKit&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/jessesquires/JSQDataSourcesKit&quot;&gt;JSQDataSourcesKit&lt;/a&gt; in some sense was always kind of experimental and academic. It doesn’t do any diffing and also has separate infrastructure for tables and collections like ReactiveLists, as it similarly pre-dated those modern collection view APIs for making list layouts. It was primarily concerned with constructing type-safe data sources that eliminated the boilerplate associated with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UITableViewDataSource&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewDataSource&lt;/code&gt;. Ultimately, the generics were too unwieldy — especially at the time, given the state of generics in Swift. See my post, &lt;em&gt;&lt;a href=&quot;https://www.jessesquires.com/blog/2020/04/14/deprecating-jsqdatasourceskit/&quot;&gt;Deprecating JSQDataSourcesKit&lt;/a&gt;&lt;/em&gt;, for more details. What JSQDataSourcesKit got right was the idea of using generics to provide type-safety, though it was not executed very well.&lt;/p&gt;

&lt;h3 id=&quot;goals&quot;&gt;Goals&lt;/h3&gt;

&lt;p&gt;The primary goals of ReactiveCollectionsKit are to remove all of the boilerplate associated with building lists and collections, and making your views effortless to update. See the example code above.&lt;/p&gt;

&lt;p&gt;UIKit has advanced a lot over the years, but correctly setting up a &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewDiffableDataSource&lt;/code&gt;&lt;/a&gt; is quite a bit of work. It is tedious and still requires a lot of boilerplate. It is also error prone when it comes to getting diffing to work correctly. (More on that in a future post.)&lt;/p&gt;

&lt;p&gt;More specifically, when using this library, you no longer need to interact with any of these collection view APIs:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reloadData()&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reconfigureItems(at:)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reloadSections(_:)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reloadItems(at:)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;performBatchUpdates(_:completion:)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;All &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewDataSource&lt;/code&gt; methods&lt;/li&gt;
  &lt;li&gt;All &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewDelegate&lt;/code&gt; methods&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The benefit and delight in using ReactiveCollectionsKit is that you simply define the structure of your data. You create your collection of items, organize them into sections, and pass this to the library to handle everything else.&lt;/p&gt;

&lt;h3 id=&quot;future-work&quot;&gt;Future Work&lt;/h3&gt;

&lt;p&gt;All of this experience and knowledge has culminated in me writing this new library. ReactiveCollectionsKit aims to keep all the good ideas and designs from these other libraries, while also addressing their shortcomings. I wrote or maintained all of them, so hopefully I got it right this time!&lt;/p&gt;

&lt;p&gt;While this library is in a great state and ready for production, it is not yet finished. There is &lt;a href=&quot;https://github.com/jessesquires/ReactiveCollectionsKit/issues&quot;&gt;plenty to do&lt;/a&gt;! The most commonly used collection view APIs have been implemented, but there are some missing features — like supporting expanding/collapsing sections, for example. Another big feature on my list is to implement a more SwiftUI-like API using Swift &lt;a href=&quot;https://www.hackingwithswift.com/swift/5.4/result-builders&quot;&gt;result builders&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are interested in getting involved, please &lt;a href=&quot;https://github.com/jessesquires/ReactiveCollectionsKit/issues&quot;&gt;open an issue&lt;/a&gt; or send me a &lt;a href=&quot;https://github.com/jessesquires/ReactiveCollectionsKit/pulls&quot;&gt;pull request&lt;/a&gt;!&lt;/p&gt;

&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/10/18/introducing-reactivecollectionskit/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/10/16/kintsugi/</id>
        <link href="https://www.jessesquires.com/blog/2024/10/16/kintsugi/" />
        <title>Kintsugi</title>
        <published>2024-10-16T11:51:41-07:00</published>
        <updated>2024-10-16T11:51:41-07:00</updated>

        <category term="essays" />
        <category term="tea" /><category term="kintsugi" />
        <summary type="html">&lt;p&gt;Kintsugi — no, not &lt;a href=&quot;https://github.com/Lightricks/Kintsugi&quot;&gt;&lt;em&gt;that&lt;/em&gt; Kintsugi&lt;/a&gt; — is the the &lt;a href=&quot;https://en.wikipedia.org/wiki/Kintsugi&quot;&gt;Japanese art&lt;/a&gt; of repairing broken pottery. The process involves mending the breaks in the ceramics with urushi lacquer, essentially gluing the pieces together and filling in any gaps to smooth it out. At the end, you finish off the cracks with a gold powder, which highlights and emphasizes the flaws in the piece rather than attempting to conceal them. The result is a restored piece of pottery or teaware that can now be put back to use.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;Kintsugi — no, not &lt;a href=&quot;https://github.com/Lightricks/Kintsugi&quot;&gt;&lt;em&gt;that&lt;/em&gt; Kintsugi&lt;/a&gt; — is the the &lt;a href=&quot;https://en.wikipedia.org/wiki/Kintsugi&quot;&gt;Japanese art&lt;/a&gt; of repairing broken pottery. The process involves mending the breaks in the ceramics with urushi lacquer, essentially gluing the pieces together and filling in any gaps to smooth it out. At the end, you finish off the cracks with a gold powder, which highlights and emphasizes the flaws in the piece rather than attempting to conceal them. The result is a restored piece of pottery or teaware that can now be put back to use.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;However, Kintsugi is not merely a practice centered around mending shattered ceramics. It is also a philosophy about embracing imperfections and the history that produced them. Kintsugi reorients us into treating flaws and breakage as things that are inherent to an object — and to ourselves. Imperfections are part of us and our history, and so is their repair. Kintsugi deliberately accentuates and celebrates the cracks of a formerly broken piece of pottery &lt;strong&gt;with gold&lt;/strong&gt;, rather than trying to hide or disguise them. It is a beautiful thing.&lt;/p&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;p&gt;About a year ago, I dropped the lid of my favorite teapot, shattering it into multiple pieces. It was a Yixing clay pot that I had seasoned and used for exclusively brewing pu-erhs. With high-end clay teaware, you typically choose only one kind of tea to brew. This is because the unglazed surfaces of the pot absorb traces of the tea. Think of it like seasoning a cast iron skillet. Yixing clay teaware is &lt;em&gt;not cheap&lt;/em&gt;, but the burden of this accident was not merely a monetary loss. This pot also represented &lt;em&gt;an investment&lt;/em&gt; in brewing pu-erhs — for &lt;em&gt;years&lt;/em&gt;.&lt;/p&gt;

&lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;col&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;d-flex justify-content-center col-12  col-sm-8 col-md-6 &quot;&gt;
        &lt;figure&gt;
            &lt;img class=&quot;img-thumbnail img-fluid&quot; src=&quot;/img/blog/kintsugi-broken.jpg&quot; title=&quot;Broken Yixing clay teapot&quot; alt=&quot;Broken Yixing clay teapot&quot; /&gt;
            &lt;div class=&quot;row&quot;&gt;
                &lt;div class=&quot;col-12 d-flex justify-content-center&quot;&gt;
                
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/figure&gt;
    &lt;/div&gt;
    &lt;div class=&quot;col&quot;&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I was so sad. My favorite pot for my favorite style of tea was now unusable. I adore this pot. This pot is one of a kind — handmade, not mass produced. I had to figure out how to fix it. I found &lt;a href=&quot;https://chimahaga.com/collections/kits&quot;&gt;a kintsugi repair kit online&lt;/a&gt; and did my best. The process involves 6 distinct steps. Each step takes a couples hours and you need to wait about a week after each step for things to dry. The entire process from start to finish should last about 2 months — if you are diligent. If you are like me, this 2-month project will be spread out over an entire year. I can’t say I’m proud of that, but that’s simply how most of my side projects go — because life keeps happening. We’re embracing imperfections, right? 😅&lt;/p&gt;

&lt;p&gt;If you are interested in the complete process, you &lt;a href=&quot;https://www.youtube.com/watch?v=HSQWRxaKEyw&quot;&gt;can watch this tutorial video&lt;/a&gt; that accompanies the repair kit.&lt;/p&gt;

&lt;p&gt;I’m certainly no expert in kintsugi. And I’m not sure if kintsugi is even the recommended approach for repairing Yixing clay. I’m willing to bet this is not the best option. However, it was the &lt;em&gt;only&lt;/em&gt; option accessible to me. So, here we are. Luckily, it worked! I recently finished up the last steps, my teapot is repaired, and I can &lt;em&gt;finally&lt;/em&gt; start using it again.&lt;/p&gt;

&lt;p&gt;This was my first attempt at kintsugi and there are certainly some flaws in the repair itself. My lines are not super straight and thin and elegant. But done is better than perfect, right? I suppose you really have to appreciate the irony of the imperfections in the repair itself.&lt;/p&gt;

&lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;col&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;d-flex justify-content-center col-12  col-sm-8 col-md-6 &quot;&gt;
        &lt;figure&gt;
            &lt;img class=&quot;img-thumbnail img-fluid&quot; src=&quot;/img/blog/kintsugi-repaired.jpg&quot; title=&quot;Repaired Yixing clay teapot&quot; alt=&quot;Repaired Yixing clay teapot&quot; /&gt;
            &lt;div class=&quot;row&quot;&gt;
                &lt;div class=&quot;col-12 d-flex justify-content-center&quot;&gt;
                
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/figure&gt;
    &lt;/div&gt;
    &lt;div class=&quot;col&quot;&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;It was satisfying and rewarding to mend something that was damaged, but in a way that does not try to hide what was broken. The gold finish is like the beautiful scar tissue that patches our deepest wounds, a reminder of where we’ve been what we’re capable of moving through. Sometimes, things are more beautiful after they’ve been broken and put back together.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/10/16/kintsugi/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/09/09/swift-observable-macro/</id>
        <link href="https://www.jessesquires.com/blog/2024/09/09/swift-observable-macro/" />
        <title>SwiftUI&apos;s Observable macro is not a drop-in replacement for ObservableObject</title>
        <published>2024-09-09T07:32:37-07:00</published>
        <updated>2024-09-09T07:32:37-07:00</updated>

        <category term="software-dev" />
        <category term="swift" /><category term="swiftui" /><category term="ios" /><category term="macos" />
        <summary type="html">&lt;p&gt;SwiftUI’s new &lt;a href=&quot;https://developer.apple.com/documentation/Observation/Observable()&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Observable&lt;/code&gt; macro&lt;/a&gt; is not a drop-in replacement for &lt;a href=&quot;https://developer.apple.com/documentation/Combine/ObservableObject&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObservableObject&lt;/code&gt;&lt;/a&gt;. I learned of a subtle difference in behavior the hard way. Hopefully, I can save you from the same headache I experienced and save you some time.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;SwiftUI’s new &lt;a href=&quot;https://developer.apple.com/documentation/Observation/Observable()&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Observable&lt;/code&gt; macro&lt;/a&gt; is not a drop-in replacement for &lt;a href=&quot;https://developer.apple.com/documentation/Combine/ObservableObject&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObservableObject&lt;/code&gt;&lt;/a&gt;. I learned of a subtle difference in behavior the hard way. Hopefully, I can save you from the same headache I experienced and save you some time.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;h3 id=&quot;background&quot;&gt;Background&lt;/h3&gt;

&lt;p&gt;The new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Observable&lt;/code&gt; macro was introduced last year with iOS 17 and macOS 14. It was advertised as (mostly) a drop-in replacement for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObservableObject&lt;/code&gt;, but there is a significant behavior difference regarding initialization. You need to use the &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/stateobject&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@StateObject&lt;/code&gt; property wrapper&lt;/a&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObservableObject&lt;/code&gt; and use the &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/state&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; property wrapper&lt;/a&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Observable&lt;/code&gt;. This is the key difference and the underlying cause for the difference in initialization behavior. In other words, the issue is &lt;em&gt;not really&lt;/em&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObservableObject&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Observable&lt;/code&gt;, but with their associated property wrappers.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@StateObject&lt;/code&gt; property wrapper initializer has the definition:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wrappedValue&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;thunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;@autoclosure&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;@escaping&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ObjectType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; property wrapper initializer has the definition:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wrappedValue&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I think the &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro&quot;&gt;official migration guide&lt;/a&gt; should have a loud warning about this, but unfortunately it does not.&lt;/p&gt;

&lt;p&gt;This is a &lt;strong&gt;significant&lt;/strong&gt; difference. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@StateObject&lt;/code&gt; receives an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@autoclosure&lt;/code&gt; for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wrappedValue&lt;/code&gt; parameter while &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; simply receives the value. The result is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@StateObject&lt;/code&gt; can not only defer initialization of the received value, but it will only ever initialize the value once. When using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt;, the initializer for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Value&lt;/code&gt; will be called every single time SwiftUI destroys and rebuilds the view hierarchy. Depending on how you’ve constructed your views and types, this is either a non-issue or results in incredibly confusing and buggy behavior that is not obvious unless you are aware of this subtle difference.&lt;/p&gt;

&lt;h3 id=&quot;example&quot;&gt;Example&lt;/h3&gt;

&lt;p&gt;Let’s look at a brief example of the differences. First, we’ll consider the “old” way of doing things using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObservableObject&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@StateObject&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ObservableObject&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;@Published&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;@main&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyApp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;@StateObject&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;model&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Scene&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;WindowGroup&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;MainView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;environmentObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, let’s convert this example to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Observable&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; by following the &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro&quot;&gt;migration guide&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;@Observable&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyModel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;@main&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyApp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;@State&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;model&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Scene&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;WindowGroup&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;MainView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;the-bug&quot;&gt;The bug&lt;/h3&gt;

&lt;p&gt;Unfortunately, switching to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Observable&lt;/code&gt; caused a significant and difficult to debug problem in my app. There were three issues with how I had architected my app using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObservableObject&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;First, unlike the example code above, I was declaring my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@StateObject&lt;/code&gt; from within my main root &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; object — &lt;strong&gt;not&lt;/strong&gt; at the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;App&lt;/code&gt; level.&lt;/li&gt;
  &lt;li&gt;Next, in the initializer of my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObservableObject&lt;/code&gt;, I was loading cached data from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserDefaults&lt;/code&gt; and storing these values in my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Published&lt;/code&gt; properties.&lt;/li&gt;
  &lt;li&gt;Finally, my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObservableObject&lt;/code&gt; was registered to listen for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NS/UIApplication.willTerminateNotification&lt;/code&gt; notification and saved its data back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserDefaults&lt;/code&gt; when quitting the app.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s a simplified version of what the code looked like &lt;em&gt;after&lt;/em&gt; migrating to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Observable&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;@Observable&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyModel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// read cached values from UserDefaults&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UserDefaults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;standard&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;forKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;my_value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;kt&quot;&gt;NotificationCenter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;default&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;publisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;willTerminateNotification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sink&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// save values to UserDefaults&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;UserDefaults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;standard&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;forKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;my_value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cancellables&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MainView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;@State&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;model&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObservableObject&lt;/code&gt; before the migration, the code above behaved as you would expect. My data would load and save correctly. I was using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserDefaults&lt;/code&gt; because I only needed to store a few lightweight values and I wanted to keep everything simple. I was using app lifecycle notifications because &lt;a href=&quot;/blog/2024/06/29/swiftui-scene-phase/&quot;&gt;SwiftUI scene phase does not work&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The result of migrating to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Observable&lt;/code&gt; (using the code above) was unpredictable behavior because of the changes in initialization that I described above. Previously, my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObservableObject&lt;/code&gt; (remember, using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@StateObject&lt;/code&gt;) was &lt;em&gt;only ever initialized once&lt;/em&gt;, because the initializer for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@StateObject&lt;/code&gt; receives an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@autoclosure&lt;/code&gt;. This meant that reading from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserDefaults&lt;/code&gt; only occurred once and registering for the notifications only occurred once for the lifetime of the app.&lt;/p&gt;

&lt;p&gt;However, with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Observable&lt;/code&gt; (and thus, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt;), the initializer for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MyModel&lt;/code&gt; is called &lt;em&gt;every single time SwiftUI decides to rebuild the view.&lt;/em&gt; Under-the-hood, when state changes occur, SwiftUI will destroy and rebuild the view. This means your view struct (in this case, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainView&lt;/code&gt;) will get re-initialized often. This implicitly re-initializes all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; variables, too. However, SwiftUI preserves the &lt;em&gt;original&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; property value and applies that value before initialization completes. The flow looks like this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Initialize the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; variables (these are &lt;em&gt;new&lt;/em&gt; objects)&lt;/li&gt;
  &lt;li&gt;Call the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; initializer&lt;/li&gt;
  &lt;li&gt;Reset the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; variables back to the &lt;em&gt;previously initialized and stored&lt;/em&gt; objects (discarding objects initialized in step 1)&lt;/li&gt;
  &lt;li&gt;Initialization is complete&lt;/li&gt;
  &lt;li&gt;Execute the view’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body&lt;/code&gt; (using the correct values from step 3)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What I observed is that all those new, initial instances of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MyModel&lt;/code&gt; (from step 1 above) &lt;strong&gt;linger around in memory indefinitely&lt;/strong&gt;. I verified this via logging and Xcode’s memory graph debugger. Occasionally and non-deterministically, some of those &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MyModel&lt;/code&gt; instances will get deallocated. This seems like a bug in SwiftUI.&lt;/p&gt;

&lt;p&gt;Note that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; still displays the correct data because the original object is reset to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; property (step 3 above).&lt;/p&gt;

&lt;p&gt;The reason this became a problem in my particular situation was because my model object was listening for the notification, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NS/UIApplication.willTerminateNotification&lt;/code&gt;. All those extra instances of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MyModel&lt;/code&gt; lingering around in memory were still observing this notification. When triggered, each instance would save whatever data it had to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserDefaults&lt;/code&gt;, resulting in a non-deterministic “last write wins” scenario. Upon relaunching the app, who knows was random data would load from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserDefaults&lt;/code&gt;. Yikes. This was admittedly not the best design, but it worked well for my purposes.&lt;/p&gt;

&lt;h3 id=&quot;the-fix&quot;&gt;The fix&lt;/h3&gt;

&lt;p&gt;I suppose the moral of this story is, &lt;em&gt;“I was holding it wrong.”&lt;/em&gt; But honestly, if you cannot design your API such that a user is &lt;em&gt;unable&lt;/em&gt; to hold it wrong in the first place, then it is not entirely the user’s fault when things go wrong. However, I do think the fact that random &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; objects linger around somewhere in memory in the opaque abyss of SwiftUI’s state management mechanism is a real problem.&lt;/p&gt;

&lt;p&gt;The correct way to architect your app is to store all (app-level or global) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; properties in your top-level &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;App&lt;/code&gt; struct, which does not get repeatedly destroyed and rebuilt like SwiftUI &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; objects. (And really, your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@StateObject&lt;/code&gt; properties should also be declared in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;App&lt;/code&gt; struct too.) There were some technical reason why I did not initially do this, but the details are not important. I was able to rework my design to make it work. If you cannot do that, then you need to ensure whatever object you store in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; &lt;em&gt;does nothing&lt;/em&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init()&lt;/code&gt; and does not manage any state in response to app lifecycle notifications or something similar. Because scene phase is broken in a number of ways, I &lt;a href=&quot;/blog/2024/06/29/swiftui-scene-phase/&quot;&gt;recommend using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NS/UIApplicationDelegateAdaptor&lt;/code&gt; instead&lt;/a&gt;. Only scene-level or view-specific state properties should be declared at the view-level.&lt;/p&gt;

&lt;p&gt;An aside: when using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; you will also want to ensure that your object’s initializer does not perform any &lt;em&gt;expensive&lt;/em&gt; operations either. That will also result in a very bad time for you.&lt;/p&gt;

&lt;h3 id=&quot;additional-reading&quot;&gt;Additional reading&lt;/h3&gt;

&lt;p&gt;These posts on other initialization quirks of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@State&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@StateObject&lt;/code&gt; are also worth reading.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://jaredsinclair.com/2024/03/14/state-object-autoclosure.html&quot;&gt;Be Careful When You Initialize a State Object&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://samwize.com/2024/05/08/do-not-init-state-externally-in-swiftui-view/&quot;&gt;Do NOT init State externally in SwiftUI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/09/09/swift-observable-macro/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/07/09/uitest-improvements-in-xcode-16/</id>
        <link href="https://www.jessesquires.com/blog/2024/07/09/uitest-improvements-in-xcode-16/" />
        <title>UI testing improvements in Xcode 16</title>
        <published>2024-07-09T12:45:08-07:00</published>
        <updated>2024-07-09T12:45:08-07:00</updated>

        <category term="software-dev" />
        <category term="xcode" /><category term="ios" /><category term="macos" /><category term="testing" /><category term="ui-testing" />
        <summary type="html">&lt;p&gt;While the new &lt;a href=&quot;https://developer.apple.com/xcode/swift-testing/&quot;&gt;Swift Testing&lt;/a&gt; framework announced this year at WWDC24 is getting a lot of attention, there are some notable improvements coming to UI testing in &lt;a href=&quot;https://developer.apple.com/documentation/xctest/user_interface_tests&quot;&gt;XCTest&lt;/a&gt; in Xcode 16.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;While the new &lt;a href=&quot;https://developer.apple.com/xcode/swift-testing/&quot;&gt;Swift Testing&lt;/a&gt; framework announced this year at WWDC24 is getting a lot of attention, there are some notable improvements coming to UI testing in &lt;a href=&quot;https://developer.apple.com/documentation/xctest/user_interface_tests&quot;&gt;XCTest&lt;/a&gt; in Xcode 16.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;Xcode 16 &lt;a href=&quot;https://developer.apple.com/documentation/xcode-release-notes/xcode-16-release-notes#XCTest&quot;&gt;introduces two new APIs&lt;/a&gt; in XCTest for UI testing.&lt;/p&gt;

&lt;p&gt;The first is &lt;a href=&quot;https://developer.apple.com/documentation/xctest/xcuielement/4391535-waitfornonexistence&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;waitForNonExistence(withTimeout:)&lt;/code&gt;&lt;/a&gt;, which provides the inverse of the existing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;waitForExistence(timeout:)&lt;/code&gt; API. Finally! This is such a welcome change. Often in UI testing it is more semantic to wait for an element to &lt;em&gt;disappear&lt;/em&gt; rather than &lt;em&gt;appear&lt;/em&gt; — for example, waiting for a loading indicator or waiting for a &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicontentunavailableview&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIContentUnavailableView&lt;/code&gt;&lt;/a&gt; to disappear. Previously, you would have to roll your own implementation or awkwardly use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;waitForExistence(timeout:)&lt;/code&gt; and negate the result — both options are cumbersome and inefficient.&lt;/p&gt;

&lt;p&gt;Here’s an example. Suppose your view displays an initial loading state while fetching data, which then disappears once loading completes.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testLoadingView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;contentView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;otherElements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;content_view&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;XCTAssertTrue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;contentView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;waitForExistence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Content view should appear&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;loadingView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;staticTexts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;loading_view&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;XCTAssertTrue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loadingView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Content should be loading initially&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;kt&quot;&gt;XCTAssertTrue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loadingView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;waitForNonExistence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;withTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Loading should complete&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;XCTAssertFalse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loadingView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Side note: the inconsistency in the naming of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timeout:&lt;/code&gt; parameter label for these two functions is a bit odd. The new method uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;withTimeout&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timeout&lt;/code&gt;. I would prefer if they were both consistently named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timeout&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;waitForExistence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;TimeInterval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;waitForNonExistence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;withTimeout&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;TimeInterval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The second new API is &lt;a href=&quot;https://developer.apple.com/documentation/xctest/xcuielement/4395161-wait&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wait(for:toEqual:timeout:)&lt;/code&gt;&lt;/a&gt;, which waits for a property value of an element to equal a new value. This is useful for when the contents of an existing view should be updated and you want to verify the update happened. The most common use case here is likely for checking the contents of labels, text fields, or text views that change based on state updates or user interaction. Previously, there was not a great way to achieve this without introducing artificial timeouts in your test, or changing the UI element’s &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiaccessibilityidentification/1623132-accessibilityidentifier&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.accessibilityIdentifier&lt;/code&gt;&lt;/a&gt; in your app when its contents updated and then checking for the existence of the new identifier.&lt;/p&gt;

&lt;p&gt;Continuing with the example above, suppose your loading view does not disappear but instead updates with a new message. Initially, the view displays &lt;em&gt;“Loading…“&lt;/em&gt; and then displays &lt;em&gt;“Loading Complete!”&lt;/em&gt; when data is finished loading.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testLoadingView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;contentView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;otherElements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;content_view&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;XCTAssertTrue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;contentView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;waitForExistence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Content view should appear&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;loadingView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;staticTexts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;loading_view&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;XCTAssertTrue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loadingView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Content should be loading initially&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;XCTAssertEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loadingView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Loading...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Label should initially display &apos;Loading...&apos;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;kt&quot;&gt;XCTAssertTrue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;loadingView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;toEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Loading Complete!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;Label should update when loading is done to say &apos;Loading Complete!&apos;&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Additionally, you could test for failure states as well. In this scenario, perhaps when data fails to load, you could display &lt;em&gt;“Oops, there was an error!”&lt;/em&gt; in the label. You could write a similar UI test for this situation.&lt;/p&gt;

&lt;p&gt;Unfortunately, in my testing &lt;a href=&quot;https://developer.apple.com/documentation/xctest/xcuielement/4395161-wait&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wait(for:toEqual:timeout:)&lt;/code&gt;&lt;/a&gt; did not work as expected. In fact, the sample test above will fail. In order to get this test to pass, I had to introduce an artificial timeout before calling and checking &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wait(for:toEqual:timeout:)&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loadingView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;waitForExistence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;XCTAssertTrue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;loadingView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;toEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Loading Complete!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;Label should update when loading is done to say &apos;Loading Complete!&apos;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Hopefully this bug gets fixed before the final release of Xcode 16.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/07/09/uitest-improvements-in-xcode-16/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/07/04/app-store-screenshot-changes/</id>
        <link href="https://www.jessesquires.com/blog/2024/07/04/app-store-screenshot-changes/" />
        <title>Our App Store screenshot nightmare is (almost) over</title>
        <published>2024-07-04T11:55:25-07:00</published>
        <updated>2024-07-04T11:55:25-07:00</updated>

        <category term="software-dev" />
        <category term="app-store" /><category term="mac-app-store" /><category term="ios" /><category term="macos" /><category term="apple" /><category term="screenshots" /><category term="wwdc" />
        <summary type="html">&lt;p&gt;I previously wrote about how the requirements for &lt;a href=&quot;/blog/2024/01/16/app-store-screenshot-requirements/&quot;&gt;screenshots on the App Store have become increasingly burdensome over the years&lt;/a&gt;. It is truly a nightmare. But today I have good news to share! Our nightmare is coming to an end, as changes to screenshot requirements were announced at WWDC24 this year.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;I previously wrote about how the requirements for &lt;a href=&quot;/blog/2024/01/16/app-store-screenshot-requirements/&quot;&gt;screenshots on the App Store have become increasingly burdensome over the years&lt;/a&gt;. It is truly a nightmare. But today I have good news to share! Our nightmare is coming to an end, as changes to screenshot requirements were announced at WWDC24 this year.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;App Store Connect is getting an update and &lt;strong&gt;only one set of screenshots will be required&lt;/strong&gt; for iPhone and iPad! This news was somewhat buried toward the end of the session, &lt;a href=&quot;https://developer.apple.com/wwdc24/10063&quot;&gt;&lt;em&gt;What’s new in App Store Connect&lt;/em&gt;&lt;/a&gt;. Unfortunately, it was only a brief bullet point with no extra details — but I’ll take it. It’s not clear &lt;em&gt;which&lt;/em&gt; devices will be allowed for a single set of screenshots, but it will likely be one of the larger device sizes for both iPhone and iPad from the latest models.&lt;/p&gt;

&lt;p&gt;It also is not clear &lt;em&gt;when&lt;/em&gt; these changes will take effect in App Store Connect. Currently, the old requirements remain. I suspect that App Store Connect updates will coincide with the final releases of iOS 18 and macOS 15 later this year. So, we’ve got a few months to wait.&lt;/p&gt;

&lt;p&gt;What about macOS screenshots? There are &lt;a href=&quot;/blog/2024/01/16/app-store-screenshot-requirements/&quot;&gt;problems there, too&lt;/a&gt; — specifically the allowed sizes and aspect ratios. Unfortunately, it looks like the existing requirements for macOS screenshots will remain. At least we made progress on iOS.&lt;/p&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;p&gt;There were some other welcome changes announced in &lt;a href=&quot;https://developer.apple.com/wwdc24/10063&quot;&gt;&lt;em&gt;What’s new in App Store Connect&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;More opportunities to get your app discovered via “Featuring Nominations”&lt;/li&gt;
  &lt;li&gt;TestFlight is getting an updated invitation experience and criteria for public links that allow you to restrict invites to things like specific devices and operating systems&lt;/li&gt;
  &lt;li&gt;Deep links to custom product pages&lt;/li&gt;
  &lt;li&gt;A new “Promote Your App” feature to help you generate marketing assets&lt;/li&gt;
  &lt;li&gt;Notifications when your app gets featured on the App Store (another &lt;em&gt;“finally!”&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/07/04/app-store-screenshot-changes/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/06/29/swiftui-scene-phase/</id>
        <link href="https://www.jessesquires.com/blog/2024/06/29/swiftui-scene-phase/" />
        <title>SwiftUI app lifecycle: issues with ScenePhase and using AppDelegate adaptors</title>
        <published>2024-06-29T14:25:23-07:00</published>
        <updated>2024-06-29T14:25:23-07:00</updated>

        <category term="software-dev" />
        <category term="ios" /><category term="macos" /><category term="swiftui" />
        <summary type="html">&lt;p&gt;SwiftUI introduced the &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/scenephase&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt;&lt;/a&gt; API in iOS 14 and macOS 11. This was SwiftUI’s answer to handling application lifecycle events. At the same time, SwiftUI introduced &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/uiapplicationdelegateadaptor&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIApplicationDelegateAdaptor&lt;/code&gt;&lt;/a&gt; for iOS and &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/nsapplicationdelegateadaptor&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSApplicationDelegateAdaptor&lt;/code&gt;&lt;/a&gt; for macOS, which allow you to provide an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AppDelegate&lt;/code&gt; on both platforms to receive additional application lifecycle events and other events that were missing from SwiftUI at the time. Unfortunately, many of those application event APIs are still missing and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; has a number of bugs (or at least, unexpected behavior).&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;SwiftUI introduced the &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/scenephase&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt;&lt;/a&gt; API in iOS 14 and macOS 11. This was SwiftUI’s answer to handling application lifecycle events. At the same time, SwiftUI introduced &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/uiapplicationdelegateadaptor&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIApplicationDelegateAdaptor&lt;/code&gt;&lt;/a&gt; for iOS and &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/nsapplicationdelegateadaptor&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSApplicationDelegateAdaptor&lt;/code&gt;&lt;/a&gt; for macOS, which allow you to provide an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AppDelegate&lt;/code&gt; on both platforms to receive additional application lifecycle events and other events that were missing from SwiftUI at the time. Unfortunately, many of those application event APIs are still missing and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; has a number of bugs (or at least, unexpected behavior).&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;h3 id=&quot;limitations-of-scenephase-events-and-view-lifecycle-events&quot;&gt;Limitations of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; events (and view lifecycle events)&lt;/h3&gt;

&lt;p&gt;The issue with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; is that it is too limited, having only 3 states: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;active&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inactive&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background&lt;/code&gt;. In a perfect world, an application should not &lt;em&gt;necessarily&lt;/em&gt; need to know whether it is launching for the first time or being terminated. In practice, however, these are extremely relevant and important application events. There are many scenarios in which knowing the distinction between a first (or “cold”) launch and merely returning to the active state is helpful. Similarly, there are good reasons to treat a background state and termination differently.&lt;/p&gt;

&lt;p&gt;I think &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; should expand to include both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;didLaunch&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;willTerminate&lt;/code&gt; events, because attempting to infer these states is cumbersome and error prone, if not impossible. Or even better, what I would really like is a separate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AppPhase&lt;/code&gt; API that allows you to handle application-level lifecycle events separately from window scenes.&lt;/p&gt;

&lt;p&gt;The concept of “scenes” really centers around &lt;em&gt;windows&lt;/em&gt;, not the &lt;em&gt;entire application&lt;/em&gt;, and that is part of the problem with the API. The scene phases correlate to and model the lifecycle of application windows. It just so happens that on iOS your app only ever has a single window, so the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; API is a better fit on that platform — and, in fact, it does work better on iOS. (Yes, iPadOS can now have multiple windows, but let’s not get into that mess.) The differences between all the various platforms make working with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; even more difficult because it feels like SwiftUI is trying to force a uniform model across all platforms, despite key differences in their paradigms.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/scenephase/background&quot;&gt;documentation&lt;/a&gt; states that you should &lt;em&gt;“expect an app that enters the background phase to terminate.”&lt;/em&gt; This is unfortunate because it is possible (and probably common) for users to switch between apps and quickly return to your app. If there is no distinction between temporarily being in the background and a complete termination, then your app could end up doing a lot of unnecessary tear down and set up work. As you’ll see below, this is actually terrible advice for a macOS app.&lt;/p&gt;

&lt;p&gt;UIKit and AppKit both provide additional granularity on top of these 5 main events, with “will” and “did” APIs. For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;applicationWillResignActive()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;applicationDidResignActive()&lt;/code&gt;. I can see the motivation for simplifying the SwiftUI APIs, but there are scenarios where SwiftUI is too limiting and it is valuable to know that an event &lt;em&gt;is about to happen&lt;/em&gt; versus knowing an event &lt;em&gt;has already happened&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The same limitations exist with SwiftUI’s &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/view-fundamentals#responding-to-view-life-cycle-updates&quot;&gt;view lifecycle&lt;/a&gt; methods, &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/view/onappear(perform:)&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onAppear()&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/view/ondisappear(perform:)&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onDisappear()&lt;/code&gt;&lt;/a&gt; — which, according to the docs these should be more accurately named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;willAppear()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;didDisappear()&lt;/code&gt;. The fact that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onAppear()&lt;/code&gt; is called &lt;em&gt;before&lt;/em&gt; a view appears, but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onDisappear()&lt;/code&gt; is called &lt;em&gt;after&lt;/em&gt; a view disappears is inconsistent and confusing.&lt;/p&gt;

&lt;h3 id=&quot;bugs-and-quirks-with-scenephase&quot;&gt;Bugs and quirks with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Aside from being too limited, there are also a number of bugs with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; in SwiftUI. Perhaps some of this behavior is intended, but if so, it is certainly unexpected.&lt;/p&gt;

&lt;p&gt;Here is a minimal implementation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; to demonstrate. Note that this snippet is printing the results of all changes to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scenePhase&lt;/code&gt; so that you can observe what is happening.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;@main&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyApp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;@Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(\&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scenePhase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;scenePhase&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Scene&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;WindowGroup&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;ContentView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;onChange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scenePhase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;initial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oldValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newValue&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ScenePhase: &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;oldValue&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; -&amp;gt; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newValue&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;behavior-on-ios&quot;&gt;Behavior on iOS&lt;/h4&gt;

&lt;p&gt;On iOS, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onChange(of:)&lt;/code&gt; works mostly as expected. Here are some common scenarios and the associated scene phase transitions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Initial app launch: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inactive&lt;/code&gt; &amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;active&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Move to background: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;active&lt;/code&gt; &amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inactive&lt;/code&gt; &amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Return to foreground: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background&lt;/code&gt; &amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inactive&lt;/code&gt; &amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;active&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Temporary background (like toggling Control Center): toggles between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;active&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inactive&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;App termination: if currently &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;active&lt;/code&gt;, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inactive&lt;/code&gt; &amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background&lt;/code&gt;. Notably, if the app is already in the background and you force quit it, then no scene phase transitions occur. This means you must treat all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background&lt;/code&gt; events as if the app is being terminated. As mentioned above, this is not optimal in all scenarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One quirk in behavior depends on whether you opt to receive the initial value, or not by passing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;initial:&lt;/code&gt; parameter. If you pass &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;, then the first &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onChange(of:)&lt;/code&gt; event for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; is from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inactive&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inactive&lt;/code&gt;, then you receive the update to transition to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;active&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Unexpectedly, the main view receives &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onAppear()&lt;/code&gt; &lt;strong&gt;before&lt;/strong&gt; your app receives the scene phase change. In the sample above, that would be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ContentView&lt;/code&gt;. That’s like receiving &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viewWillAppear()&lt;/code&gt; on your root view controller &lt;strong&gt;before&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application(_:didFinishLaunchingWithOptions:)&lt;/code&gt; gets called. That sequence of events does not make sense to me and raises concerns about how you should accurately architect your app startup flow in SwiftUI.&lt;/p&gt;

&lt;p&gt;The sparse &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; transitions above stand in stark contrast to the rich and granular events provided by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIApplicationDelegate&lt;/code&gt;.&lt;/p&gt;

&lt;h4 id=&quot;behavior-on-macos&quot;&gt;Behavior on macOS&lt;/h4&gt;

&lt;p&gt;On macOS, the behavior of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onChange(of:)&lt;/code&gt; is very different and very unexpected. Here are a similar set of scenarios as above and the associated scene phase transitions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Initial app launch: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;active&lt;/code&gt; &amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;active&lt;/code&gt;. (what? lol)&lt;/li&gt;
  &lt;li&gt;Move to background (making a different application front most and active): &lt;strong&gt;no events&lt;/strong&gt;.
    &lt;ul&gt;
      &lt;li&gt;Return to foreground: &lt;strong&gt;no events&lt;/strong&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Move to background (by hiding the application, cmd-H): &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;active&lt;/code&gt; &amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background&lt;/code&gt;.
    &lt;ul&gt;
      &lt;li&gt;Return to foreground: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background&lt;/code&gt; &amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;active&lt;/code&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Closing the main window (but keep application running): no scene phase events. But you do receive &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onDisappear()&lt;/code&gt; for the main view.&lt;/li&gt;
  &lt;li&gt;App termination (via cmd-Q): &lt;strong&gt;no events&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As mentioned above, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/scenephase/background&quot;&gt;documentation&lt;/a&gt; states that you should &lt;em&gt;“expect an app that enters the background phase to terminate.”&lt;/em&gt; That is a terrible assumption for a Mac app, based on the transitions listed above. Hiding the application (cmd-H) is the only way I have found to trigger a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background&lt;/code&gt; scene phase on macOS. Preparing for termination when the user simply hides your app does not make sense. What a terrible user experience that would be!&lt;/p&gt;

&lt;p&gt;If you pass &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;initial:&lt;/code&gt; parameter on macOS, you receive no initial scene phase change.&lt;/p&gt;

&lt;p&gt;In my testing, I have never been able to get a macOS app to transition to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inactive&lt;/code&gt; scene phase. It is not clear if this is by design, or if this is a bug. It seems like a bug.&lt;/p&gt;

&lt;p&gt;Unlike on iOS, macOS receives &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onAppear()&lt;/code&gt; for the main view in the correct order. That is, the scene phase becomes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;active&lt;/code&gt; &lt;em&gt;and then&lt;/em&gt; the app receives &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onAppear()&lt;/code&gt; for the main view. But unexpectedly, when hiding the application on macOS, you &lt;em&gt;do not&lt;/em&gt; receive the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onDisappear()&lt;/code&gt; event. You only receive &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onDisappear()&lt;/code&gt; when closing a window, and notably, &lt;em&gt;no&lt;/em&gt; scene phase events occur.&lt;/p&gt;

&lt;p&gt;If it isn’t obvious, it is actually impossible to rely solely on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; in a macOS application where you need access to application lifecycle events, or even reliable app window lifecycle events. On macOS &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; events are outright insufficient and stand in even starker contrast to the rich and granular events provided by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSApplicationDelegate&lt;/code&gt; and, of course, &lt;a href=&quot;https://developer.apple.com/documentation/appkit/nswindowdelegate&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSWindowDelegate&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;app-delegate-adaptors-are-discouraged&quot;&gt;App delegate adaptors are discouraged&lt;/h3&gt;

&lt;p&gt;Despite all of the shortcomings and unreliable behavior in the APIs I have listed above, the documentation for both
&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/uiapplicationdelegateadaptor&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIApplicationDelegateAdaptor&lt;/code&gt;&lt;/a&gt; on iOS and &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/nsapplicationdelegateadaptor&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSApplicationDelegateAdaptor&lt;/code&gt;&lt;/a&gt; on macOS, both &lt;em&gt;discourage&lt;/em&gt; their use with a big scary warning:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;Manage an app’s life cycle events without using an app delegate whenever possible. For example, prefer to handle changes in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; instead of relying on delegate callbacks […]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is interesting, especially on macOS, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; literally just does not work and cannot replace delegate callbacks at all. In my experience, on both platforms but mostly on macOS, you must use an app delegate if you need reliable and granular app lifecycle events.&lt;/p&gt;

&lt;h3 id=&quot;using-app-delegate-adaptors-and-their-issues&quot;&gt;Using app delegate adaptors (and their issues)&lt;/h3&gt;

&lt;p&gt;If you instead want to respond to application lifecycle events &lt;em&gt;outside&lt;/em&gt; of SwiftUI, you can provide an app delegate. First, you need to create the properties in your SwiftUI app.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#if canImport(AppKit)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;@NSApplicationDelegateAdaptor&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;appDelegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSAppDelegate&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#endif&lt;/span&gt;

&lt;span class=&quot;cp&quot;&gt;#if canImport(UIKit)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;@UIApplicationDelegateAdaptor&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;appDelegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIAppDelegate&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#endif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then you can implement an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AppDelegate&lt;/code&gt; for iOS:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIKit&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIAppDelegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIApplicationDelegate&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIWindow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;didFinishLaunchingWithOptions&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;launchOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;LaunchOptionsKey&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applicationDidBecomeActive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applicationWillResignActive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applicationDidEnterBackground&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applicationWillEnterForeground&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applicationWillTerminate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And you can implement an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AppDelegate&lt;/code&gt; for macOS:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AppKit&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSAppDelegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSApplicationDelegate&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applicationWillFinishLaunching&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;notification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Notification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applicationDidFinishLaunching&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;notification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Notification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applicationWillHide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;notification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Notification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applicationDidHide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;notification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Notification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applicationDidBecomeActive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;notification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Notification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applicationWillResignActive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;notification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Notification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applicationDidResignActive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;notification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Notification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applicationWillTerminate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;notification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Notification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pro tip: because app delegates are defined as protocols, you could instead create a single class that conforms to both protocols. This can be a good strategy for sharing code between platforms.&lt;/p&gt;

&lt;p&gt;On macOS, you receive all the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSApplicationDelegate&lt;/code&gt; callbacks as expected. Everything works. Contrary to the documentation, I would &lt;em&gt;avoid&lt;/em&gt; attempting to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; &lt;em&gt;at all&lt;/em&gt; on macOS.&lt;/p&gt;

&lt;p&gt;On iOS, app delegate adapters are a different story. They don’t work as I would expect. Of all the callbacks defined in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIApplicationDelegate&lt;/code&gt; above, only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application(_:didFinishLaunchingWithOptions:)&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;applicationWillTerminate(_:)&lt;/code&gt; are called in a SwiftUI app. This behavior occurs even if you have &lt;em&gt;opted out&lt;/em&gt; of multiple window support on iOS by providing the correct plist values for &lt;a href=&quot;https://developer.apple.com/documentation/bundleresources/information_property_list/uiapplicationscenemanifest&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIApplicationSceneManifest&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want more granular scene events on iOS, you must provide a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SceneDelegate&lt;/code&gt; class that conforms to &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiwindowscenedelegate&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIWindowSceneDelegate&lt;/code&gt;&lt;/a&gt;. You can still &lt;a href=&quot;https://developer.apple.com/documentation/bundleresources/information_property_list/uiapplicationscenemanifest/uiapplicationsupportsmultiplescenes&quot;&gt;turn off multiple windows&lt;/a&gt;, but you must provide a scene configuration and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UISceneDelegateClassName&lt;/code&gt; in your plist. Only then will you receive the corresponding callbacks.&lt;/p&gt;

&lt;p&gt;Here’s an example plist:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;plist&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1.0&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;UIApplicationSceneManifest&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;UIApplicationSupportsMultipleScenes&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;false/&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;UISceneConfigurations&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;UIWindowSceneSessionRoleApplication&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
                    &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;UISceneConfigurationName&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
                    &lt;span class=&quot;nt&quot;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;Default Configuration&lt;span class=&quot;nt&quot;&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
                    &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;UISceneDelegateClassName&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
                    &lt;span class=&quot;nt&quot;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;$(PRODUCT_MODULE_NAME).SceneDelegate&lt;span class=&quot;nt&quot;&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/plist&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And an updated app delegate, along with a default scene delegate:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIAppDelegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIApplicationDelegate&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;didFinishLaunchingWithOptions&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;launchOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;LaunchOptionsKey&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applicationWillTerminate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// MARK: UISceneSession Lifecycle&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                     &lt;span class=&quot;n&quot;&gt;configurationForConnecting&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;connectingSceneSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UISceneSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                     &lt;span class=&quot;nv&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIScene&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ConnectionOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UISceneConfiguration&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UISceneConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Default Configuration&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;sessionRole&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connectingSceneSession&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;didDiscardSceneSessions&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;sceneSessions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;UISceneSession&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SceneDelegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIResponder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIWindowSceneDelegate&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIWindow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;scene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;scene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIScene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
               &lt;span class=&quot;n&quot;&gt;willConnectTo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UISceneSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
               &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;connectionOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIScene&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ConnectionOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scene&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as?&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIWindowScene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sceneDidDisconnect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;scene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIScene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sceneDidBecomeActive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;scene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIScene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sceneWillResignActive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;scene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIScene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sceneWillEnterForeground&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;scene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIScene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sceneDidEnterBackground&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;scene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIScene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that for SwiftUI, all you need to do is declare the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@UIApplicationDelegateAdaptor&lt;/code&gt; above for this all to work. The scene delegate is determined from your app plist and everything seems to “just work” in terms of setting up the delegates.&lt;/p&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;In the code above, I have used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;print(#function)&lt;/code&gt; to print all the events to the console as they happen. This was very instructive for discerning how all of these APIs work together — or, sometimes, don’t. I encourage you to put it all together in a sample app if you are curious to experiment with and visualize the sequence of callbacks as they happen.&lt;/p&gt;

&lt;p&gt;On one hand, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; APIs can get you somewhat far on iOS. But if you need more granular control, you need to define an app delegate, a scene delegate, and provide the corresponding scene manifest in your app’s plist. If you are going through all that trouble, you might as well structure your app to have a UIKit shell instead of trying to rely entirely on SwiftUI. On the other hand, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScenePhase&lt;/code&gt; APIs are essentially useless on macOS and you must use an app delegate — although this is much easier to set up. For complex Mac apps, an AppKit shell is probably the best approach.&lt;/p&gt;

&lt;p&gt;There are probably some simple utility apps on both platforms that will never need to worry about all the issues I have described here, but for serious applications, you will run into these issues. Furthermore, there are &lt;em&gt;even more&lt;/em&gt; roles and responsibilities that app delegates have that I have not discussed here — if you need any of that functionality for your app, then you &lt;em&gt;really must&lt;/em&gt; have an app delegate no matter what.&lt;/p&gt;

&lt;p&gt;After all these years, it is disappointing that SwiftUI still does not offer these necessary and fundamental APIs for building applications on both platforms. SwiftUI needs more robust and reliable APIs for managing the app lifecycle, window lifecycles, and view lifecycles.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/06/29/swiftui-scene-phase/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/06/05/swift-concurrency-non-sendable-closures/</id>
        <link href="https://www.jessesquires.com/blog/2024/06/05/swift-concurrency-non-sendable-closures/" />
        <title>Swift concurrency hack for passing non-sendable closures: Uncheck yourself before you wreck yourself</title>
        <published>2024-06-05T10:30:35-07:00</published>
        <updated>2024-06-05T18:24:16-07:00</updated>

        <category term="software-dev" />
        <category term="swift" /><category term="concurrency" />
        <summary type="html">&lt;p&gt;If you have attempted to adopt &lt;a href=&quot;https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/&quot;&gt;Swift Concurrency&lt;/a&gt; in your codebase, you have certainly needed to address dozens — likely, hundreds — of warnings and errors. Sometimes the issues can be resolved by addressing them directly. That is, your code was incorrect and you simply have to fix it to make it correct. In other scenarios, the resolution is not so straightforward. In particular, it is difficult to satisfy the compiler when working with APIs that you do not own that have not been updated for concurrency. Or, you may have found yourself in a situation where &lt;em&gt;you&lt;/em&gt; know your code is correct, but &lt;em&gt;the compiler&lt;/em&gt; is unable to verify its correctness — either because of a few remaining bugs in Swift Concurrency, or because you are using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@preconcurrency&lt;/code&gt; APIs.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;If you have attempted to adopt &lt;a href=&quot;https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/&quot;&gt;Swift Concurrency&lt;/a&gt; in your codebase, you have certainly needed to address dozens — likely, hundreds — of warnings and errors. Sometimes the issues can be resolved by addressing them directly. That is, your code was incorrect and you simply have to fix it to make it correct. In other scenarios, the resolution is not so straightforward. In particular, it is difficult to satisfy the compiler when working with APIs that you do not own that have not been updated for concurrency. Or, you may have found yourself in a situation where &lt;em&gt;you&lt;/em&gt; know your code is correct, but &lt;em&gt;the compiler&lt;/em&gt; is unable to verify its correctness — either because of a few remaining bugs in Swift Concurrency, or because you are using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@preconcurrency&lt;/code&gt; APIs.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;One of the warnings you have probably seen multiple times is &lt;em&gt;“Capture of ‘variable’ with non-sendable type in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Sendable&lt;/code&gt; closure.”&lt;/em&gt; I was confronted with this warning in a project recently and I want to share the hack for how I worked around it. The issue was the result of a combination of factors I mentioned above. I was interacting with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@preconcurrency&lt;/code&gt; APIs &lt;strong&gt;and&lt;/strong&gt; I knew my code was concurrency-safe, but I was unable to accurately express that to the compiler.&lt;/p&gt;

&lt;h3 id=&quot;background&quot;&gt;Background&lt;/h3&gt;

&lt;p&gt;First, let’s discuss the context in which I was dealing with this concurrency warning. I have been working on a project that uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewDiffableDataSource&lt;/code&gt; and I am wrapping the UIKit APIs with something more user-friendly. For the purposes of this post, I have omitted much of the complexity to provide a clear, simple example.&lt;/p&gt;

&lt;p&gt;I have a custom diffable data source that performs the diffing on a background thread:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;@MainActor&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DiffableDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UICollectionViewDiffableDataSource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;typealias&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Snapshot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSDiffableDataSourceSnapshot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;typealias&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SnapshotCompletion&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;@MainActor&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Void&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;diffingQueue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DispatchQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;diffingQueue&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applyDiff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SnapshotCompletion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;diffingQueue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;animatingDifferences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Per &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource/3375795-apply&quot;&gt;the documentation&lt;/a&gt;, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;completion&lt;/code&gt; closure is always called on the main queue and you can call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apply(_:animatingDifferences:completion:)&lt;/code&gt; from a background queue:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;[…]&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;completion&lt;/strong&gt;&lt;br /&gt;
A closure to execute when the animations are complete. This closure has no return value and takes no parameters. The system calls this closure from the main queue.&lt;/p&gt;

  &lt;p&gt;[…]&lt;/p&gt;

  &lt;p&gt;You can safely call this method from a background queue, but you must do so consistently in your app. Always call this method exclusively from the main queue or from a background queue.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Also note that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UICollectionViewDiffableDataSource&lt;/code&gt; is annotated with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@MainActor @preconcurrency&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With the strictest concurrency checking enabled, the code above produces 2 warnings:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applyDiff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SnapshotCompletion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;diffingQueue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;animatingDifferences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//                                                               ^&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Capture of &apos;completion&apos; with non-sendable type &apos;DiffableDataSource.SnapshotCompletion?&apos; (aka &apos;Optional&amp;lt;@MainActor () -&amp;gt; ()&amp;gt;&apos;) in a `@Sendable` closure&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Converting function value of type &apos;@MainActor () -&amp;gt; Void&apos; to &apos;() -&amp;gt; Void&apos; loses global actor &apos;MainActor&apos;; this is an error in Swift 6&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One solution would be to make the completion closure &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Sendable&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applyDiff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;@escaping&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;@Sendable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;diffingQueue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;animatingDifferences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(Note that we cannot use the defined typealias &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SnapshotCompletion&lt;/code&gt; in this case, and it must not be optional.)&lt;/p&gt;

&lt;p&gt;However, this will not work. This API updates the collection view to reflect the state of the data in the snapshot and, as mentioned, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;completion&lt;/code&gt; is called on the main thread. Callers of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;applyDiff(snapshot:)&lt;/code&gt; do additional UI updates and animations, or otherwise deal with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@MainActor&lt;/code&gt; members and types in this completion closure. Those members and types cannot be marked as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Sendable&lt;/code&gt;. For example, the owner of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DiffableDataSource&lt;/code&gt; instance could be a view controller. Furthermore, I do not want to impose a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Sendable&lt;/code&gt; restriction upon callers.&lt;/p&gt;

&lt;p&gt;Thus, making the closure &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Sendable&lt;/code&gt; produces the following kinds of errors at the call site:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dataSource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;applyDiff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Call to main actor-isolated instance method &apos;someMethod()&apos; in a synchronous nonisolated context&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Main actor-isolated property &apos;someProperty&apos; can not be referenced from a Sendable closure; this is an error in Swift 6&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Callers could wrap the offending lines in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Task { @MainActor in }&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainActor.assumeIsolated { }&lt;/code&gt; to silence these issues. But, that’s a burden for callers. Not to mention, the wrapper API does not accurately communicate what is happening here. We do not want a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Sendable () -&amp;gt; Void&lt;/code&gt; closure. We want a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@MainActor () -&amp;gt; Void&lt;/code&gt; closure.&lt;/p&gt;

&lt;p&gt;So, we have situation where the Swift compiler is telling us that the closure being captured needs to be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Sendable&lt;/code&gt; but we cannot make it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Sendable&lt;/code&gt;. It is also telling us that the closure loses its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@MainActor&lt;/code&gt; but we know that the closure will always be called from the main queue. Because of these two problems, we need to find a way to work around the warnings and coerce the compiler into doing what we want.&lt;/p&gt;

&lt;h3 id=&quot;solution-its-a-hack&quot;&gt;Solution (It’s a hack)&lt;/h3&gt;

&lt;p&gt;We can wrap the completion closure in another type that is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@unchecked Sendable&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UncheckedCompletion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;@unchecked&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Sendable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;typealias&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Block&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Void&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nf&quot;&gt;dispatchPrecondition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;onQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
                &lt;span class=&quot;nf&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will silence the warning about &lt;em&gt;“capturing a non-sendable type in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Sendable&lt;/code&gt; closure.”&lt;/em&gt; Again, UIKit guarantees that this completion closure will always be called on the main thread, and we can use a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatchPrecondition()&lt;/code&gt; to verify this is happening.&lt;/p&gt;

&lt;p&gt;We can update our API to use this new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UncheckedCompletion&lt;/code&gt; wrapper.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applyDiff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UncheckedCompletion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;diffingQueue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;animatingDifferences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, exposing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UncheckedCompletion&lt;/code&gt; to callers is also not a great API. We should hide this detail. We can wrap this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;applyDiff()&lt;/code&gt; method with another that uses the original &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SnapshotCompletion&lt;/code&gt; typealias.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applyDiff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SnapshotCompletion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;applyDiff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UncheckedCompletion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;//                                                                 ^ wrapped in UncheckedCompletion&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applyDiff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UncheckedCompletion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;diffingQueue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;animatingDifferences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//                                                               ^ access underlying closure&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And now, the public API looks exactly the same as before to callers, but they can safely use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@MainActor&lt;/code&gt; members and types in the completion closure without any warnings or errors.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dataSource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;applyDiff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// do anything here safely with @MainActor with no warnings or errors&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;is-this-good&quot;&gt;Is this good?&lt;/h3&gt;

&lt;p&gt;Is this a good idea? I am actually not sure! But, it seems like the best thing to do in this scenario. If you are facing a similar situation — namely, &lt;strong&gt;you know&lt;/strong&gt; a captured closure is always called on the main thread and you cannot make it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Sendable&lt;/code&gt; — this might be a good solution for you too! However, this is probably &lt;em&gt;a bad idea&lt;/em&gt; to attempt to generalize. Use wisely!&lt;/p&gt;

&lt;div class=&quot;d-block bg-light-dark pt-3 px-3 pb-0 my-3 border border-2 border-secondary rounded update-notice&quot;&gt;
    &lt;h5 id=&quot;updated-05-june-2024&quot;&gt;
        &lt;a href=&quot;#updated-05-june-2024&quot; class=&quot;text-reset&quot;&gt;&lt;i class=&quot;bi bi-link&quot;&gt;&lt;/i&gt; Update&lt;/a&gt;
        &lt;small class=&quot;text-body-secondary&quot; title=&quot;05 Jun 2024 06:24:16 PM PDT&quot;&gt;
            &lt;i&gt;05 June 2024&lt;/i&gt;
        &lt;/small&gt;
    &lt;/h5&gt;
    
&lt;p&gt;As anticipated, &lt;a href=&quot;https://mastodon.social/@mattiem&quot;&gt;Matt Massicotte&lt;/a&gt; has come to the rescue, &lt;a href=&quot;https://mastodon.social/@mattiem/112565464652182320&quot;&gt;offering a simpler solution here&lt;/a&gt;. While my clever hack works, we can instead make the closure both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Sendable&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@MainActor&lt;/code&gt;. After that, we can simply wrap calling the completion closure in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainActor.assumeIsolated { }&lt;/code&gt;. Here are the changes needed:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// The addition of @Sendable is bizarre, but Swift 5.10 needs it.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Swift 6 (via SE-0434) will make it unnecessary.&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;typealias&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SnapshotCompletion&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;@Sendable&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;@MainActor&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Void&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applyDiff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SnapshotCompletion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;diffingQueue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// UIKit guarantees `completion` is called on the main queue.&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;animatingDifferences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// when you know its on the main actor, perhaps from documentation, but it isn&apos;t&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// encoded in the API, you can use dynamic isolation to make it work&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;MainActor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;assumeIsolated&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nf&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is great. It is much less code. I’m not sure why I didn’t &lt;em&gt;try&lt;/em&gt; using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Sendable @MainActor&lt;/code&gt; for the closure. It’s probably because I assumed that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@MainActor&lt;/code&gt; &lt;em&gt;implied&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Sendable&lt;/code&gt; — which apparently &lt;em&gt;it should&lt;/em&gt; and &lt;em&gt;it will&lt;/em&gt; after &lt;a href=&quot;https://github.com/apple/swift-evolution/blob/main/proposals/0434-global-actor-isolated-types-usability.md&quot;&gt;SE-0434&lt;/a&gt; in Swift 6.&lt;/p&gt;

&lt;p&gt;Anyway, the general idea of this hack might still be useful in other contexts or scenarios — especially if you cannot adopt Swift 6 and need to stay on Swift 5.10.&lt;/p&gt;

&lt;/div&gt;

&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/06/05/swift-concurrency-non-sendable-closures/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/05/29/swiftpm-package-resolved-xcode/</id>
        <link href="https://www.jessesquires.com/blog/2024/05/29/swiftpm-package-resolved-xcode/" />
        <title>Workaround: Xcode deletes Package.resolved file and produces &apos;missing package product&apos; errors</title>
        <published>2024-05-29T19:06:41-07:00</published>
        <updated>2024-05-30T09:49:22-07:00</updated>

        <category term="software-dev" />
        <category term="xcode" /><category term="ios" /><category term="macos" /><category term="bugs" /><category term="swiftpm" /><category term="swift" /><category term="git" />
        <summary type="html">&lt;p&gt;More and more Apple Platform developers are migrating away from &lt;a href=&quot;https://cocoapods.org&quot;&gt;CocoaPods&lt;/a&gt; in favor the &lt;a href=&quot;https://www.swift.org/documentation/package-manager/&quot;&gt;Swift Package Manager&lt;/a&gt;, which is Apple’s first-party tool for managing and integrating dependencies. While it is still &lt;a href=&quot;/blog/2020/02/24/replacing-cocoapods-with-swiftpm/&quot;&gt;not quite a complete replacement&lt;/a&gt; for CocoaPods, it is getting closer. Unfortunately, SwiftPM’s integration with Xcode still has a number of shortcomings, even though it was introduced with &lt;a href=&quot;https://developer.apple.com/documentation/xcode-release-notes/xcode-11-release-notes&quot;&gt;Xcode 11&lt;/a&gt; — 4 years ago. The worst bug is that Xcode frequently and randomly deletes the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt;, which in turn produces dozens or hundreds of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;missing package product&apos;&lt;/code&gt; errors. Here’s how I’ve worked around this bug on a team I work on.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;More and more Apple Platform developers are migrating away from &lt;a href=&quot;https://cocoapods.org&quot;&gt;CocoaPods&lt;/a&gt; in favor the &lt;a href=&quot;https://www.swift.org/documentation/package-manager/&quot;&gt;Swift Package Manager&lt;/a&gt;, which is Apple’s first-party tool for managing and integrating dependencies. While it is still &lt;a href=&quot;/blog/2020/02/24/replacing-cocoapods-with-swiftpm/&quot;&gt;not quite a complete replacement&lt;/a&gt; for CocoaPods, it is getting closer. Unfortunately, SwiftPM’s integration with Xcode still has a number of shortcomings, even though it was introduced with &lt;a href=&quot;https://developer.apple.com/documentation/xcode-release-notes/xcode-11-release-notes&quot;&gt;Xcode 11&lt;/a&gt; — 4 years ago. The worst bug is that Xcode frequently and randomly deletes the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt;, which in turn produces dozens or hundreds of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;missing package product&apos;&lt;/code&gt; errors. Here’s how I’ve worked around this bug on a team I work on.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;h3 id=&quot;the-bug&quot;&gt;The bug&lt;/h3&gt;

&lt;p&gt;Xcode seems to specifically delete &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; when changing branches in git. Then, depending on how large your project is and how many Swift packages are included, you’ll see dozens or hundreds or thousands of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;Missing package product &amp;lt;product_name&amp;gt;&apos;&lt;/code&gt; errors.&lt;/p&gt;

&lt;p&gt;However, I have also experienced Xcode deleting the file for seemingly no reason at all. In fact, I’ve had moments where Xcode deletes the file immediately after I restore it via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git restore&lt;/code&gt; — and I can just do that infinitely. I put the file back, then Xcode deletes it. In that beautiful scenario, the Xcode project must be closed and re-opened.&lt;/p&gt;

&lt;p&gt;Searching the Apple Developer Forums &lt;a href=&quot;https://developer.apple.com/forums/search?q=missing+package+product&quot;&gt;returns dozens of results&lt;/a&gt; for this bug. Here is &lt;a href=&quot;https://developer.apple.com/forums/thread/755772&quot;&gt;a recent one&lt;/a&gt; that correctly describes the problem. Here is &lt;a href=&quot;https://developer.apple.com/forums/thread/687275&quot;&gt;another post from 2021&lt;/a&gt; that has new replies from 2 weeks ago. There is also &lt;a href=&quot;https://forums.swift.org/t/missing-package-product-error-for-all-local-swift-packages-when-switching-git-branches/38041&quot;&gt;this post from the Swift.org forums&lt;/a&gt; — from June 2020 — describing the issue. If you read through that thread, you will see the problem was briefly fixed around Xcode 12, but regressed in either the 12.5 or 13.0 release. This forum thread also has new comments from the past 2 weeks (including from yours truly). Unfortunately, the Swift forums are not the right place to report bugs in Xcode.&lt;/p&gt;

&lt;p&gt;Most “solutions” on the forums revolve around some magical combination of cleaning your project (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmd-shift-K&lt;/code&gt;), deleting Xcode’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DerivedData/&lt;/code&gt;, running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;File &amp;gt; Packages &amp;gt; Reset Package Caches&lt;/code&gt;, running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;File &amp;gt; Packages &amp;gt; Resolve Package Versions&lt;/code&gt;, and closing and re-opening Xcode. All of this is very heavy-handed and there’s a much lighter weight approach you can take to work around it.&lt;/p&gt;

&lt;p&gt;This bug is happening in the latest release, Xcode 15.4 (15F31d). It is a widespread problem reported across the various forums, as well as on Mastodon and other platforms. Any developer on a team that is using SwiftPM with Xcode is experiencing this bug daily. Furthermore, running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xcodebuild -resolvePackageDependencies&lt;/code&gt; seems to have no effect and it does not regenerate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;the-packageresolved-file-and-git&quot;&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; file and git&lt;/h3&gt;

&lt;p&gt;First, what is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; and why is it important? This file is equivalent to &lt;a href=&quot;https://guides.cocoapods.org/using/the-podfile.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Podfile.lock&lt;/code&gt;&lt;/a&gt; in CocoaPods — similar to &lt;a href=&quot;https://bundler.io/guides/using_bundler_in_applications.html#gemfilelock&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt;&lt;/a&gt; in Ruby/Bundler, or &lt;a href=&quot;https://docs.npmjs.com/cli/v7/configuring-npm/package-lock-json&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package-lock.json&lt;/code&gt;&lt;/a&gt; in Node.js/npm. This file is a manifest that describes the exact versions used for every package dependency. It allows SwiftPM to always install the exact same packages on multiple machines, for example the machines of each member on a team and CI machines.&lt;/p&gt;

&lt;p&gt;It turns out, it’s just a JSON file. Here’s an example of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; for an app that imports one library called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Foil&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;pins&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;identity&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;foil&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;kind&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;remoteSourceControl&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://github.com/jessesquires/Foil&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;state&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;revision&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;bc08a46268cb3bb22fee2c8465d97e6d7bf981e1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;5.0.1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; is necessary to resolve the exact packages at their exact specified versions, it should be checked-in to source control.&lt;/p&gt;

&lt;p&gt;If you are working with an Xcode project, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; is located at:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;MyApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you are working with an Xcode workspace, the file is located at:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;MyApp.xcworkspace/xcshareddata/swiftpm/Package.resolved
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When working with only Swift packages, your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.swift&lt;/code&gt; file defines all of your package dependencies. This plays a similar role to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Podfile&lt;/code&gt; in CocoaPods. With SwiftPM’s Xcode integration, the project file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;project.pbxproj&lt;/code&gt; — everyone’s favorite file — maintains the list of Swift package dependencies and their pinned versions. Again, this fulfills the same role as the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Podfile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The project file is located at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MyApp.xcodeproj/project.pbxproj&lt;/code&gt;. If you search the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;project.pbxproj&lt;/code&gt; file for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;XCRemoteSwiftPackageReference&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;XCSwiftPackageProductDependency&lt;/code&gt;, you will find all the Swift package references for your project. For the example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; file above, here is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;project.pbxproj&lt;/code&gt; representation:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/* Begin XCRemoteSwiftPackageReference section */
0B3E8A0A28AD7D9C006FB785 /* XCRemoteSwiftPackageReference &quot;Foil&quot; */ = {
    isa = XCRemoteSwiftPackageReference;
    repositoryURL = &quot;https://github.com/jessesquires/Foil&quot;;
    requirement = {
        kind = upToNextMajorVersion;
        minimumVersion = 5.0.0;
    };
};
/* End XCRemoteSwiftPackageReference section */
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For small teams or individuals that already include their entire &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.xcodeproj&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.xcworkspace&lt;/code&gt; bundles in git, there is nothing else you need to do. However, most teams explicitly ignore &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.xcodeproj&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.xcworkspace&lt;/code&gt; — ironically, because of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;project.pbxproj&lt;/code&gt;, which is a nightmare to resolve when you have conflicts in git. These teams typically use &lt;a href=&quot;https://github.com/yonaskolb/XcodeGen&quot;&gt;Xcodegen&lt;/a&gt; or a similar tool to generate their project files, thus entirely avoiding merge conflicts on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;project.pbxproj&lt;/code&gt;. In this scenario, including the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; file but ignoring everything else requires a bit of work. Here are the git ignore rules needed:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# .gitignore file&lt;/span&gt;

MyApp.xcworkspace/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;MyApp.xcworkspace/xcshareddata/
MyApp.xcworkspace/xcshareddata/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;MyApp.xcworkspace/xcshareddata/swiftpm/
MyApp.xcworkspace/xcshareddata/swiftpm/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;MyApp.xcworkspace/xcshareddata/swiftpm/Package.resolved
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that with tools like Xcodegen, you &lt;a href=&quot;https://github.com/yonaskolb/XcodeGen/blob/master/Docs/ProjectSpec.md#swift-package&quot;&gt;define your packages&lt;/a&gt; in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;project.yml&lt;/code&gt; file, which essentially is a replacement for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;project.pbxproj&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;d-block bg-light-dark pt-3 px-3 pb-0 my-3 border border-2 border-secondary rounded update-notice&quot;&gt;
    &lt;h5 id=&quot;updated-30-may-2024&quot;&gt;
        &lt;a href=&quot;#updated-30-may-2024&quot; class=&quot;text-reset&quot;&gt;&lt;i class=&quot;bi bi-link&quot;&gt;&lt;/i&gt; Update&lt;/a&gt;
        &lt;small class=&quot;text-body-secondary&quot; title=&quot;30 May 2024 09:49:22 AM PDT&quot;&gt;
            &lt;i&gt;30 May 2024&lt;/i&gt;
        &lt;/small&gt;
    &lt;/h5&gt;
    
&lt;p&gt;Thanks to &lt;a href=&quot;https://mastodon.social/@kylebshr/112528585781867745#.&quot;&gt;Kyle Bashour for pointing out&lt;/a&gt; a couple of details I got wrong about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;project.pbxproj&lt;/code&gt;. This section has been corrected.&lt;/p&gt;

&lt;/div&gt;

&lt;h3 id=&quot;handling-packageresolved-deletions&quot;&gt;Handling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; deletions&lt;/h3&gt;

&lt;p&gt;Now that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; file is saved in git, we can easily handle when Xcode deletes it by simply restoring it via git. For teams that ignore their project and workspace files and use Xcodegen (or similar), you also have the issue where Xcodegen deletes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; when it regenerates your project file. If you are also still using CocoaPods in conjunction with SwiftPM, then you have the issue where CocoaPods will delete &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; when it regenerates your workspace file.&lt;/p&gt;

&lt;p&gt;When you notice the file gets deleted, you can restore it using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git restore&lt;/code&gt;. And then all the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;Missing package product&apos;&lt;/code&gt; errors will go away when you build your project.&lt;/p&gt;

&lt;p&gt;It is common for iOS and macOS projects to use a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Makefile&lt;/code&gt; in order to bootstrap project setup, which often includes: generating the project file via Xcodegen, installing CocoaPods via Bundler, running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pod install&lt;/code&gt;, etc. In addition, makefiles are also a great place to write “shortcut” commands that are relevant to your project, for example, linting files or running tests.&lt;/p&gt;

&lt;p&gt;Here’s a target you can add to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Makefile&lt;/code&gt; to make restoring a deleted &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; file easier.&lt;/p&gt;

&lt;div class=&quot;language-makefile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;PACKAGE_FILE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;MyApp.xcworkspace/xcshareddata/swiftpm/Package.resolved&quot;&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;.PHONY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;swiftpm&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;swiftpm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Restore Package.resolved, which gets deleted when re-generating the project/workspace.
&lt;/span&gt;    &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Or, it gets deleted by Xcode.
&lt;/span&gt;    &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Only do this if the file was completely deleted.
&lt;/span&gt;    &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Otherwise, the user could be modifying packages which updates Package.resolved, so do not git restore it.
&lt;/span&gt;    &lt;span class=&quot;err&quot;&gt;@if&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;$(PACKAGE_FILE)&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;];&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;err&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Restoring Package.resolved...&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;err&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;restore&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;$(PACKAGE_FILE)&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;err&quot;&gt;xcodebuild&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;-resolvePackageDependencies;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With this target in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Makefile&lt;/code&gt;, you can simply run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make swiftpm&lt;/code&gt; as needed.&lt;/p&gt;

&lt;p&gt;Note that this only calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git restore&lt;/code&gt; if the file is &lt;em&gt;deleted&lt;/em&gt;, in case it has been (correctly) modified after updating packages. At the end, it calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xcodebuild -resolvePackageDependencies&lt;/code&gt;, which &lt;em&gt;should&lt;/em&gt; regenerate the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; on its own, but that’s also broken. It will, however, download packages to the package cache if they are missing, which is what Xcode does when opening a project.&lt;/p&gt;

&lt;p&gt;To make this even better, you can include running this target as part of your bootstrapping process if you are using CocoaPods, Xcodegen, etc. Include this target part of your primary setup flow, and you should rarely have to run it manually.&lt;/p&gt;

&lt;p&gt;If you do not use a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Makefile&lt;/code&gt;, then you can write a simple bash script instead with the contents above.&lt;/p&gt;

&lt;h3 id=&quot;danger-accidental-deletions&quot;&gt;Danger: accidental deletions&lt;/h3&gt;

&lt;p&gt;Unfortunately (again), what we discovered on one of my teams is that the above is not foolproof. Because Xcode can (and literally &lt;em&gt;does&lt;/em&gt;) randomly delete &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; at any time, someone on your team might not notice and commit the file deletion. Oops! If you use a tool like &lt;a href=&quot;https://danger.systems/ruby/&quot;&gt;Danger&lt;/a&gt; to automate code reviews (which, &lt;em&gt;you should&lt;/em&gt;), then you can add a rule to catch this mistake.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Dangerfile&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;deleted_files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;MyApp.xcworkspace/xcshareddata/swiftpm/Package.resolved&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;fail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;It looks like you deleted `Package.resolved`. Please don&apos;t do that.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will fail a pull request if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; was deleted.&lt;/p&gt;

&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;

&lt;p&gt;To recap: make sure &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; is saved in git, use a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Makefile&lt;/code&gt; to quickly and easily restore it, and add a Danger rule to catch mistakes, if needed. This should eliminate all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;Missing package product&apos;&lt;/code&gt; errors. As a last resort, if you are still having trouble, you can run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;File &amp;gt; Packages &amp;gt; Resolve Package Versions&lt;/code&gt;. If you search for solutions on StackOverflow or elsewhere, you will find some really elaborate bash scripts that use &lt;a href=&quot;https://formulae.brew.sh/formula/fswatch&quot;&gt;fswatch&lt;/a&gt; to monitor the file system to workaround this problem. I think this is a bit overkill and prefer the simpler solutions I’ve offered here.&lt;/p&gt;

&lt;p&gt;This is all a bit ridiculous, and I would say it’s shocking that this bug still hasn’t been fixed after 4 years, but this kind of thing is not all that surprising for Apple. It is rather routine that many bugs remain unfixed and tools remain broken for years.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/05/29/swiftpm-package-resolved-xcode/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/04/29/sdk-privacy-manifests/</id>
        <link href="https://www.jessesquires.com/blog/2024/04/29/sdk-privacy-manifests/" />
        <title>The curious case of Apple&apos;s third-party SDK list for privacy manifests</title>
        <published>2024-04-29T13:37:26-07:00</published>
        <updated>2024-04-30T09:53:30-07:00</updated>

        <category term="software-dev" />
        <category term="xcode" /><category term="apple" /><category term="ios" /><category term="app-store" /><category term="privacy" />
        <summary type="html">&lt;p&gt;At last year’s WWDC, Apple &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10060/&quot;&gt;introduced privacy manifests&lt;/a&gt;. They recently sent out &lt;a href=&quot;https://developer.apple.com/news/?id=pvszzano&quot;&gt;a reminder&lt;/a&gt; that the deadline for complying with these new requirements is May 1. Privacy manifests expand on the previously introduced &lt;a href=&quot;https://www.theverge.com/2020/11/5/21551926/apple-privacy-developers-nutrition-labels-app-store-ios-14&quot;&gt;privacy “nutrition labels”&lt;/a&gt; that are self-reported by developers and displayed on the App Store. Developers must start including a privacy manifest in their apps by the aforementioned deadline, but what’s more interesting is that Apple is, for the first time, imposing these new privacy rules on third-party SDKs as well. Even more interesting is &lt;a href=&quot;https://developer.apple.com/support/third-party-SDK-requirements/&quot;&gt;the list of SDKs&lt;/a&gt; that Apple has published, which, upon inspection is quite bizarre.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;At last year’s WWDC, Apple &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10060/&quot;&gt;introduced privacy manifests&lt;/a&gt;. They recently sent out &lt;a href=&quot;https://developer.apple.com/news/?id=pvszzano&quot;&gt;a reminder&lt;/a&gt; that the deadline for complying with these new requirements is May 1. Privacy manifests expand on the previously introduced &lt;a href=&quot;https://www.theverge.com/2020/11/5/21551926/apple-privacy-developers-nutrition-labels-app-store-ios-14&quot;&gt;privacy “nutrition labels”&lt;/a&gt; that are self-reported by developers and displayed on the App Store. Developers must start including a privacy manifest in their apps by the aforementioned deadline, but what’s more interesting is that Apple is, for the first time, imposing these new privacy rules on third-party SDKs as well. Even more interesting is &lt;a href=&quot;https://developer.apple.com/support/third-party-SDK-requirements/&quot;&gt;the list of SDKs&lt;/a&gt; that Apple has published, which, upon inspection is quite bizarre.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;Historically, Apple has rarely, if ever, explicitly acknowledged &lt;em&gt;any&lt;/em&gt; third-party SDK or library. It took years for them to even acknowledge community tools like CocoaPods in Xcode’s release notes (usually when they made a change that broke it). Thus, it is interesting to see which SDKs they have deemed important or concerning enough to explicitly mandate a privacy manifest. And, in typical Apple fashion, I’m pretty sure SDKs authors were &lt;em&gt;not&lt;/em&gt; notified about this in advance. We all learned which SDKs need privacy manifests at the same time — &lt;em&gt;when the list was published&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The first few entries in the list make sense:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/abseil/abseil-cpp&quot;&gt;Abseil&lt;/a&gt;, a low-level C++ library.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/openid/AppAuth-iOS&quot;&gt;AppAuth&lt;/a&gt;, an SDK for communicating with OAuth 2.0 and OpenID Connect providers.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/AFNetworking/AFNetworking&quot;&gt;AFNetworking&lt;/a&gt; and it’s successor &lt;a href=&quot;https://github.com/Alamofire/Alamofire&quot;&gt;Alamofire&lt;/a&gt;, networking libraries that wrap Apple’s APIs, which almost every iOS developer has encountered.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/google/boringssl&quot;&gt;BoringSSL&lt;/a&gt;, a fork of OpenSSL maintained by Google.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I can see how these libraries could be concerning with regard to user privacy, they are all dealing with networking, authentication, and security (except for Abseil) — these are common vectors for privacy-related issues. Abseil is the exception, but I could see an argument for why a low-level C++ library &lt;em&gt;might&lt;/em&gt; be a concern. There are also a lot of SDKs from Google and Facebook on the list — neither of those companies have a particularly good reputation when it comes to user privacy. It makes sense for those to be included.&lt;/p&gt;

&lt;p&gt;But then… you see that the list contains UI libraries that haven’t seen significant updates or any activity for multiple years, like &lt;a href=&quot;https://github.com/SVProgressHUD/SVProgressHUD&quot;&gt;SVProgressHUD&lt;/a&gt;. Why does a library that provides a single UI component need a privacy manifest? Is it as concerning and as potentially privacy invasive as the Facebook SDK? Some of the UI-only SDKs on the list haven’t seen significant updates (or any updates at all) within the last 4-5 years. Furthermore, even AFNetworking hasn’t had an update in &lt;strong&gt;4 years&lt;/strong&gt; because it was deprecated long ago after being supplanted by Alamofire. The &lt;a href=&quot;https://github.com/AFNetworking/AFNetworking&quot;&gt;AFNetworking repo on GitHub&lt;/a&gt; has been archived and read-only for &lt;strong&gt;over a year&lt;/strong&gt;! Who’s going to bother adding a privacy manifest to that?&lt;/p&gt;

&lt;p&gt;And then… there are some entries that are simply obscure and absurd: connectivity_plus, image_picker_ios, video_player_avfoundation, file_picker. What the hell are those?! They don’t even sound like SDK or library names. I have never heard of any of these, and I’ve been involved in the iOS open source community for a decade.&lt;/p&gt;

&lt;p&gt;And then… you know what’s &lt;em&gt;even more&lt;/em&gt; bizarre about &lt;a href=&quot;https://developer.apple.com/support/third-party-SDK-requirements/&quot;&gt;this list&lt;/a&gt;? There are no links! &lt;strong&gt;There are no links to the SDK project homepages or GitHub repos.&lt;/strong&gt; It is a plain text list of names, and in some cases, seemingly random names like “file_picker”. Ok LOL. SDK and library names are not &lt;em&gt;necessarily&lt;/em&gt; unique. How are you supposed to know exactly which SDKs they are referencing with so little information? Searching for “file_picker” or “image_picker_ios” or any of the other obscure names on both &lt;a href=&quot;https://cocoapods.org&quot;&gt;CocoaPods&lt;/a&gt; and the &lt;a href=&quot;https://swiftpackageindex.com&quot;&gt;Swift Package Index&lt;/a&gt; returns &lt;strong&gt;no results&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;Finally, wouldn’t you expect some sort of reason or justification for each of these SDKs being on the list? We don’t need a 10-page essay but a brief explanation of a few sentences explaining &lt;em&gt;why&lt;/em&gt; each of these SDKs is on the list would be helpful in understanding the logic and reasoning behind it.&lt;/p&gt;

&lt;p&gt;Nothing about &lt;a href=&quot;https://developer.apple.com/support/third-party-SDK-requirements/&quot;&gt;this list&lt;/a&gt; makes any sense.&lt;/p&gt;

&lt;div class=&quot;d-block bg-light-dark pt-3 px-3 pb-0 my-3 border border-2 border-secondary rounded update-notice&quot;&gt;
    &lt;h5 id=&quot;updated-30-april-2024&quot;&gt;
        &lt;a href=&quot;#updated-30-april-2024&quot; class=&quot;text-reset&quot;&gt;&lt;i class=&quot;bi bi-link&quot;&gt;&lt;/i&gt; Update&lt;/a&gt;
        &lt;small class=&quot;text-body-secondary&quot; title=&quot;30 Apr 2024 09:53:30 AM PDT&quot;&gt;
            &lt;i&gt;30 April 2024&lt;/i&gt;
        &lt;/small&gt;
    &lt;/h5&gt;
    
&lt;p&gt;As many readers have pointed out, there are also a number of popular SDKs that really &lt;em&gt;should&lt;/em&gt; be on this list if Apple is concerned about privacy. For example, the &lt;a href=&quot;https://developers.tiktok.com/doc/getting-started-ios-download/&quot;&gt;TikTok SDK&lt;/a&gt;, &lt;a href=&quot;https://developers.google.com/admob/ios/download&quot;&gt;GoogleAds&lt;/a&gt;, and the &lt;a href=&quot;https://docs.unity.com/ads/en-us/manual/InstallingTheiOSSDK&quot;&gt;Unity Ads SDK&lt;/a&gt; are all missing from &lt;a href=&quot;https://developer.apple.com/support/third-party-SDK-requirements/&quot;&gt;the list&lt;/a&gt;, just to name a few. How strange!&lt;/p&gt;

&lt;p&gt;And apparently, all of the obscure SDK names like “file_picker” are actually &lt;a href=&quot;https://flutter.dev&quot;&gt;Flutter&lt;/a&gt; packages. Again, what an odd list!&lt;/p&gt;

&lt;/div&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;p&gt;For a company that has positioned itself as a staunch privacy advocate, this list of SDKs is slapdash at best. The lack of attention to detail, like simply including links to SDK homepages, makes the list appear like it was assembled hastily and carelessly. It makes you wonder, &lt;em&gt;how was this list compiled?&lt;/em&gt; What was the criteria for including or excluding an SDK from this list?&lt;/p&gt;

&lt;p&gt;I was venting about the list on Mastodon, and the general consensus is that it was most likely just a script dump from a static analysis of app binaries on the app store, with the sole criterion being “what are the most popular libraries” used across all apps, with some minimum threshold for inclusion. It is quite clear from the list that no one at Apple really put much thought into it. 🤡&lt;/p&gt;

&lt;p&gt;If we operate under the hypothesis that this list is merely the output of a script that someone at Apple wrote to check off the line item &lt;em&gt;“determine which third-party SDKs should be required to included privacy manifests”&lt;/em&gt;, then it all starts to make more sense. This list is ultimately the result of a popularity contest, not a thoughtful analysis of SDKs that have meaningful implications for user privacy. They couldn’t even bother to link to the projects or provide brief explanations. There’s literally an entry titled “file_picker” with no other explanation. Did anyone at Apple even look into any of these libraries? Did anyone at Apple even read through this list after some script vomited it out?&lt;/p&gt;

&lt;p&gt;When Apple imposes new privacy regulations in such a slipshod manner, how are we, as developers and as users, supposed to take this seriously? This feels like more bureaucratic security and privacy theater. Let’s all take off our shoes and throw away sealed bottles of water we purchased at the airport before we proceed through the TSA security checkpoint — meanwhile doors and wheels are falling off the damn plane.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/04/29/sdk-privacy-manifests/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/01/22/swift-scripting-broken-macos-14/</id>
        <link href="https://www.jessesquires.com/blog/2024/01/22/swift-scripting-broken-macos-14/" />
        <title>Workaround: Swift scripts importing Cocoa frameworks broken on macOS 14</title>
        <published>2024-01-22T14:31:32-08:00</published>
        <updated>2024-01-22T14:31:32-08:00</updated>

        <category term="software-dev" />
        <category term="xcode" /><category term="swift" /><category term="macos" /><category term="bugs" /><category term="swift-scripting" />
        <summary type="html">&lt;p&gt;On macOS 14 Sonoma there is a regression in Swift 5.9 which causes Swift scripts that import Cocoa frameworks to fail. This issue was first reported by &lt;a href=&quot;https://github.com/rdj&quot;&gt;@rdj&lt;/a&gt;. I discovered it myself shortly after. There is a ticket open at &lt;a href=&quot;https://github.com/apple/swift/issues/68785&quot;&gt;#68785&lt;/a&gt; on the main Swift repo on GitHub to track the issue.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;On macOS 14 Sonoma there is a regression in Swift 5.9 which causes Swift scripts that import Cocoa frameworks to fail. This issue was first reported by &lt;a href=&quot;https://github.com/rdj&quot;&gt;@rdj&lt;/a&gt;. I discovered it myself shortly after. There is a ticket open at &lt;a href=&quot;https://github.com/apple/swift/issues/68785&quot;&gt;#68785&lt;/a&gt; on the main Swift repo on GitHub to track the issue.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;Considering the following Swift script:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#!/usr/bin/swift&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AppKit&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Foundation&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;NSPasteboard&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;general&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clearContents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;NSPasteboard&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;general&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello, Swift!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;forType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello, Swift!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can run directly from the command line:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./hello.swift
Hello, Swift!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On macOS 13 and earlier, this works. Unfortunately, on macOS 14 it now fails with an error: &lt;em&gt;“JIT session error: Symbols not found”&lt;/em&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;JIT session error: Symbols not found: [ _OBJC_CLASS_$_NSPasteboard, _NSPasteboardTypeString ]
Failed to materialize symbols:

[...]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/apple/swift/issues/68785#issuecomment-1904624571&quot;&gt;current workaround&lt;/a&gt; (also posted by &lt;a href=&quot;https://github.com/rdj&quot;&gt;@rdj&lt;/a&gt;) is to update the shebang, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#!/usr/bin/swift&lt;/code&gt;, by replacing it with the following:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env DYLD_FRAMEWORK_PATH=/System/Library/Frameworks /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; you must also have Xcode installed for this to work.&lt;/p&gt;

&lt;p&gt;I’ve verified that this does indeed fix the problem!&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/01/22/swift-scripting-broken-macos-14/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/01/22/fastlane-for-indies/</id>
        <link href="https://www.jessesquires.com/blog/2024/01/22/fastlane-for-indies/" />
        <title>A simple fastlane setup for solo indie developers</title>
        <published>2024-01-22T10:57:55-08:00</published>
        <updated>2024-01-22T10:57:55-08:00</updated>

        <category term="software-dev" />
        <category term="fastlane" /><category term="automation" /><category term="ios" /><category term="macos" /><category term="app-store" /><category term="indie-dev" /><category term="ruby" />
        <summary type="html">&lt;p&gt;I recently setup &lt;a href=&quot;https://fastlane.tools&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt;&lt;/a&gt; for one of my indie apps, &lt;a href=&quot;https://www.hexedbits.com/taxatio/&quot;&gt;Taxatio&lt;/a&gt;, to automate uploading builds and metadata to the App Store — by far, one of the most tedious tasks of app development. While I had used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt; extensively before when working on teams at companies, I had never actually set it up from scratch. In this post, I want to share how to do that, as well as a lightweight configuration that I think works well for solo indie developers — folks on a team of one!&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;I recently setup &lt;a href=&quot;https://fastlane.tools&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt;&lt;/a&gt; for one of my indie apps, &lt;a href=&quot;https://www.hexedbits.com/taxatio/&quot;&gt;Taxatio&lt;/a&gt;, to automate uploading builds and metadata to the App Store — by far, one of the most tedious tasks of app development. While I had used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt; extensively before when working on teams at companies, I had never actually set it up from scratch. In this post, I want to share how to do that, as well as a lightweight configuration that I think works well for solo indie developers — folks on a team of one!&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;h3 id=&quot;philosophy&quot;&gt;Philosophy&lt;/h3&gt;

&lt;p&gt;My general philosophy when it comes to software development is &lt;em&gt;do the simplest thing first&lt;/em&gt;. (Thanks to &lt;a href=&quot;https://www.fastcompany.com/3047642/do-the-simple-thing-first-the-engineering-behind-instagram&quot;&gt;Mike Krieger&lt;/a&gt; for that one.) The first step is to get something that works that is not complicated and not over-engineered. You can always make it complicated, incomprehensible, and over-engineer it later — so why start now? 😉&lt;/p&gt;

&lt;p&gt;In this particular situation with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt;, this meant that I avoided setting it up for the initial 1.0 release. I knew I would likely run into issues with setup and configuration (&lt;em&gt;I did&lt;/em&gt;) and I did not want to waste a bunch of time figuring out (somewhat unfamiliar) tooling when I could instead spend that time simply logging on to App Store Connect to manually input my metadata and upload screenshots.&lt;/p&gt;

&lt;p&gt;After the initial release, I set out to get &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt; configured so that I would never have to do that manually again. And that’s great, because App Store Connect is &lt;a href=&quot;https://lapcatsoftware.com/articles/crappstoreconnect3.html&quot;&gt;not a very good website&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;no-cicd-service&quot;&gt;No CI/CD service&lt;/h3&gt;

&lt;p&gt;Because I’m a team of one, I do not use any CI/CD service. If &lt;em&gt;a team&lt;/em&gt; (of at least a few people) told me they did not use a CI/CD service, I would scream and implement one on my first day. The benefits of using a CI/CD service with a team are clear. But there are good reasons &lt;em&gt;not&lt;/em&gt; to do this as a solo indie dev.&lt;/p&gt;

&lt;p&gt;Initially, I was torn about this decision. After working on teams for years with robust CI/CD automation, I am convinced it is &lt;em&gt;the right way™&lt;/em&gt; to do things. However, it simply does not make sense for a solo indie developer.&lt;/p&gt;

&lt;p&gt;CI/CD services may not be &lt;em&gt;that&lt;/em&gt; expensive in terms of absolute value, depending on what you consider expensive. &lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub Actions&lt;/a&gt; and &lt;a href=&quot;https://bitrise.io&quot;&gt;Bitrise&lt;/a&gt; both have free tiers, but they are pretty limited. You will likely out-grow these, especially if you are working on multiple projects. In the next tier, Bitrise charges $90/month, while GitHub charges $4/month for the first year (after which it is unclear what they charge). My experience with Bitrise in the past was great. They tend deploy timely updates for macOS and Xcode releases. GitHub on the other hand is an exercise in frustration — their current default runners are using macOS 12, machines with macOS 13 &lt;em&gt;are still in beta&lt;/em&gt;, and there is no mention of macOS 14, the latest release since last fall.&lt;/p&gt;

&lt;p&gt;In order to determine if the cost of these services is worth it, you have to ask yourself what value do they provide? They will run your tests every time you push new commits or open a pull request, build your app, and upload your app to App Store Connect (either via their own infrastructure, or by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt;). But… couldn’t I just do all of that myself? Running my unit tests locally is not a problem — my projects are not massive like at big companies, so my test suites finish in a few minutes or less. Invoking &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt; locally is also fast and easy. I don’t need some random machines on the internet to do this for me, especially not for $1,000 per year (with Bitrise).&lt;/p&gt;

&lt;p&gt;More importantly, CI/CD services are a &lt;em&gt;maintenance burden&lt;/em&gt; — a trade-off that does not make sense for a team of one. Even though I had a good experiences with Bitrise working on teams, it still broke occasionally. Updating a configuration would inadvertently break something, or communicating with App Store Connect would break (not Bitrise’s fault), or codesigning and provisioning profiles would break, or our tests would be reported as passing even though a minor configuration change actually prevented them from running at all. On GitHub actions, you’re dealing with constantly being multiple versions behind.&lt;/p&gt;

&lt;p&gt;So, is a CI/CD service worth the &lt;em&gt;financial&lt;/em&gt; cost as well as the &lt;em&gt;maintenance&lt;/em&gt; cost? For me, the answer is a clear &lt;em&gt;no&lt;/em&gt;. The cost-benefit ratio simply does not add up. I do not want to spend time maintaining a service that only I use, when I could be using that time to work on features and fix bugs. No longer do I have to suffer through &lt;em&gt;“it works on my machine, but &lt;strong&gt;not&lt;/strong&gt; on CI”&lt;/em&gt; scenarios.&lt;/p&gt;

&lt;p&gt;Rather than &lt;em&gt;complete automation&lt;/em&gt;, which is what CI/CD services offer, I have opted for &lt;em&gt;human-initiated automation&lt;/em&gt;. I run my tests locally during development. When ready to submit to the App Store, I simply run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt;. I find this to be the best way to maximize the benefits of automation, while minimizing maintenance costs and financial costs.&lt;/p&gt;

&lt;h3 id=&quot;initial-setup-for-fastlane&quot;&gt;Initial setup for fastlane&lt;/h3&gt;

&lt;p&gt;While &lt;a href=&quot;https://github.com/fastlane/fastlane&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt;&lt;/a&gt; does seem to be actively maintained, my impression is that much of it feels like it is languishing in maintenance mode. The documentation feels quite dated, sometimes referencing Xcode 7 or 8. I also ran into a handful of tiny bugs and had to experiment with various parameters. But it does still work, so that’s great!&lt;/p&gt;

&lt;p&gt;For initial setup, &lt;a href=&quot;https://docs.fastlane.tools/getting-started/ios/setup/&quot;&gt;you can follow the docs&lt;/a&gt; and follow the prompts.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fastlane init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You’ll be prompted to authenticate to App Store Connect. For now, you can just authenticate with your Apple ID when prompted. You can switch to using an API Key after initial setup.&lt;/p&gt;

&lt;p&gt;If you already have App Store Connect configured with your app metadata and screenshots, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt; can download this for you. The initial setup will prompt to do this for you, but I ran into issues. I would suggest doing this manually, especially if your app runs on multiple platforms.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# download metadata for each platform&lt;/span&gt;
fastlane deliver download_metadata &lt;span class=&quot;nt&quot;&gt;--platform&lt;/span&gt; osx &lt;span class=&quot;nt&quot;&gt;--use_live_version&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true
&lt;/span&gt;fastlane deliver download_metadata &lt;span class=&quot;nt&quot;&gt;--platform&lt;/span&gt; ios &lt;span class=&quot;nt&quot;&gt;--use_live_version&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# download screenshots for each platform&lt;/span&gt;
fastlane deliver download_screenshots &lt;span class=&quot;nt&quot;&gt;--platform&lt;/span&gt; osx &lt;span class=&quot;nt&quot;&gt;--use_live_version&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true
&lt;/span&gt;fastlane deliver download_screenshots &lt;span class=&quot;nt&quot;&gt;--platform&lt;/span&gt; ios &lt;span class=&quot;nt&quot;&gt;--use_live_version&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You’ll only need to do this once to bootstrap your initial setup. Per the recommendations in the docs, I would git ignore the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane/screenshots/&lt;/code&gt; directory. These image files will be large, and they can easily be regenerated at any time.&lt;/p&gt;

&lt;h3 id=&quot;notes-on-using-bundler&quot;&gt;Notes on using bundler&lt;/h3&gt;

&lt;p&gt;The &lt;a href=&quot;https://docs.fastlane.tools/getting-started/ios/setup/&quot;&gt;docs&lt;/a&gt; encourage you to use &lt;a href=&quot;https://bundler.io&quot;&gt;Bundler&lt;/a&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt;. I agree that this is important for teams and CI/CD setups to ensure everyone (and &lt;em&gt;everywhere&lt;/em&gt;) is using the exact same versions of everything. However, as a solo indie dev working on only my own machine, I find this to be overkill — especially for a single gem. I simply install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt; and invoke it directly. Then I don’t need to check-in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt; into git.&lt;/p&gt;

&lt;h3 id=&quot;authentication&quot;&gt;Authentication&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.fastlane.tools/app-store-connect-api/&quot;&gt;Setting up an API Key&lt;/a&gt; is the easiest method of authentication with App Store Connect, especially when working on multiple apps. You will not be constantly prompted for your Apple ID credentials and you won’t be bothered by 2-factor auth. I found that &lt;a href=&quot;https://docs.fastlane.tools/app-store-connect-api/&quot;&gt;using a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt; API Key JSON file&lt;/a&gt; was the simplest approach for me. I save this on my machine at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Developer/AppStoreConnect/api_key.json&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;key_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;XXXXXXXXXX&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;issuer_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-----BEGIN PRIVATE KEY-----&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;XXXXXXXXXX&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;-----END PRIVATE KEY-----&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Again, not having to configure all of this on a CI machine is a joy. I don’t have to worry about accidentally leaking secrets because I misconfigured something. I also don’t have to worry about data breaches on a CI service.&lt;/p&gt;

&lt;h3 id=&quot;codesigning&quot;&gt;Codesigning&lt;/h3&gt;

&lt;p&gt;When working on a team, using Xcode’s “automatic codesigning” feature is usually a nightmare. This is why &lt;a href=&quot;http://docs.fastlane.tools/codesigning/getting-started/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt; has its own entire infrastructure for this&lt;/a&gt;. It is even more complicated and burdensome and tedious to get everything working on a CI/CD service, not to mention for everyone on a team. But, lucky for me, none of that applies to solo indie development!&lt;/p&gt;

&lt;p&gt;When working alone, Xcode’s automatic codesigning works fine. You can configure &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt; accordingly. Even better, you do not have to waste your time trying to get all of this working on a CI/CD service!&lt;/p&gt;

&lt;p&gt;I initially had some trouble getting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt; to work with automatic codesigning. First, you need to configure your Xcode project to use automatic codesigning for all relevant targets. Second, when building via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt;, you need to pass &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-allowProvisioningUpdates&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export_xcargs&lt;/code&gt; parameter. See the configuration files below.&lt;/p&gt;

&lt;h3 id=&quot;minimal-configuration&quot;&gt;Minimal configuration&lt;/h3&gt;

&lt;p&gt;Here’s the minimal configuration you’ll need for building and uploading your app.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.fastlane.tools/advanced/Appfile&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Appfile&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;app_identifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;com.example.MyApp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;apple_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;email@example.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;itc_team_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;123456&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;team_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;XXXXXXXXX&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# set other parameters as needed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.fastlane.tools/actions/deliver&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Deliverfile&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;api_key_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/PATH/TO/YOUR/api_key.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# set other parameters as needed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.fastlane.tools/actions/gym&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gymfile&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;output_directory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;./build&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# relative to your project&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Enable automatic code signing and provisioning&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;export_xcargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-allowProvisioningUpdates&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;screenshots&quot;&gt;Screenshots&lt;/h3&gt;

&lt;p&gt;The App Store &lt;a href=&quot;/blog/2024/01/16/app-store-screenshot-requirements/&quot;&gt;screenshot requirements are unpleasant to deal with&lt;/a&gt;. Luckily, &lt;a href=&quot;https://docs.fastlane.tools/getting-started/ios/screenshots/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane snapshot&lt;/code&gt;&lt;/a&gt; can help with this for iOS. Unfortunately, it simply does not work for macOS. (See &lt;a href=&quot;https://github.com/fastlane/fastlane/issues/11092&quot;&gt;#11092&lt;/a&gt;, &lt;a href=&quot;https://github.com/fastlane/fastlane/issues/11092#issuecomment-349012721&quot;&gt;#11092-comment-349012721&lt;/a&gt;, &lt;a href=&quot;https://github.com/fastlane/fastlane/issues/11092#issuecomment-349273411&quot;&gt;#11092-comment-349273411&lt;/a&gt;, &lt;a href=&quot;https://github.com/fastlane/fastlane/pull/19864&quot;&gt;#19864&lt;/a&gt;) The good news though is that you &lt;em&gt;can&lt;/em&gt; use fastlane to &lt;em&gt;upload&lt;/em&gt; your macOS screenshots to App Store Connect.&lt;/p&gt;

&lt;p&gt;For iOS, you can configure a &lt;a href=&quot;http://docs.fastlane.tools/actions/snapshot/#snapfile&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Snapfile&lt;/code&gt;&lt;/a&gt; with all the parameters you need.&lt;/p&gt;

&lt;p&gt;Here’s my lane for generating iOS screenshots. Because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt; makes it trivial, I generate screenshots for all devices. Although, I really &lt;a href=&quot;/blog/2024/01/16/app-store-screenshot-requirements/&quot;&gt;wish this weren’t necessary&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;screenshots_path_ios&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;./fastlane/screenshots/ios&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;platform&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:ios&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Capture iOS screenshots&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lane&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:screenshots&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;capture_screenshots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;scheme: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SnapshotTests-iOS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;output_directory: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;screenshots_path_ios&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;concurrent_simulators: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;devices: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;iPhone 15 Pro Max&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;iPhone 14 Plus&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;iPhone 15 Pro&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;iPhone 14&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;iPhone SE 3&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;iPad Pro (12.9-inch) (6th generation)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;iPad Pro (12.9-inch) (2nd generation)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;iPad Pro (11-inch) (4th generation)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;iPad (9th generation)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;iPad mini (5th generation)&quot;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;putting-it-all-together&quot;&gt;Putting it all together&lt;/h3&gt;

&lt;p&gt;With all of this configuration complete, you can now write your &lt;a href=&quot;http://docs.fastlane.tools/advanced/Fastfile&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fastfile&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Below is my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fastfile&lt;/code&gt; for &lt;a href=&quot;https://www.hexedbits.com/taxatio/&quot;&gt;Taxatio&lt;/a&gt;, which runs on iOS and macOS. Like I mentioned, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt; does not work for &lt;em&gt;generating&lt;/em&gt; macOS screenshots, but it does work for &lt;em&gt;uploading&lt;/em&gt; them. All you need to do is place your screenshots in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane/screenshots/&lt;/code&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt; to find them.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;screenshots_path_ios&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;./fastlane/screenshots/ios&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;screenshots_path_macos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;./fastlane/screenshots/macos&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;platform&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:ios&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Upload iOS app, metadata, and screenshots to the App Store&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lane&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:appstore_upload&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run_tests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;scheme: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Taxatio-iOS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;build_app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;scheme: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Taxatio-iOS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;output_name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Taxatio-iOS&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;upload_to_app_store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;screenshots_path: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;screenshots_path_ios&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;platform&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:mac&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Upload macOS app, metadata, and screenshots to the App Store&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lane&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:appstore_upload&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run_tests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;scheme: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Taxatio-macOS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;build_app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;scheme: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Taxatio-macOS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;output_name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Taxatio-macOS&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;upload_to_app_store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;screenshots_path: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;screenshots_path_macos&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When ready to submit, I run the following commands:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;fastlane&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ios&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appstore_upload&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fastlane&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mac&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appstore_upload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Important Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt; seems to get confused with screenshots for multiple platforms. If you provide explicit, distinct directories for each platform’s screenshots like I have above, then everything works. To me, this is also a nicer method of organization. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;upload_to_app_store&lt;/code&gt; action uses the API Key defined in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Deliverfile&lt;/code&gt;, so all you need to do is provide the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;screenshots_path&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Even though I run tests frequently during development, I have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane&lt;/code&gt; run all unit tests first as a sanity check — just in case I forget to run them.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;For building the app, I only need to provide the scheme because I’m using automatic codesigning and my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gymfile&lt;/code&gt; passes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-allowProvisioningUpdates&lt;/code&gt;. I also provide unique names for the output binaries to differentiate between platforms.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;complete-workflow&quot;&gt;Complete workflow&lt;/h3&gt;

&lt;p&gt;With all of this in place, my overall workflow is the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;I keep all of my projects in private repos on GitHub.&lt;/li&gt;
  &lt;li&gt;I write code, build, run, and test locally via Xcode.&lt;/li&gt;
  &lt;li&gt;When ready to release, I’ll generate new screenshots if needed.
    &lt;ol&gt;
      &lt;li&gt;For iOS: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane ios screenshots&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;For macOS: there are a few options. I’ll write about this in another post. Once I have my Mac app screenshots ready, I put them in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane/screenshots/macos/&lt;/code&gt;.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;When ready to upload to App Store Connect, I run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastlane [ios,macOS] appstore_upload&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;I login to App Store Connect for a quick sanity check and submit manually.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I am very happy with this lightweight setup. I have automated the tedious and error-prone aspects of dealing with the App Store, without the hassle of maintaining a CI/CD service. Even better, now that I have found a solution that works, I can bring this over to other apps in the future beginning with the 1.0 release.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/01/22/fastlane-for-indies/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/01/18/menu-bar-apps-for-sale/</id>
        <link href="https://www.jessesquires.com/blog/2024/01/18/menu-bar-apps-for-sale/" />
        <title>Mac menu bar apps for sale</title>
        <published>2024-01-18T11:26:52-08:00</published>
        <updated>2024-01-18T11:26:52-08:00</updated>

        <category term="software-dev" />
        <category term="macos" /><category term="apps" /><category term="mac-app-store" />
        <summary type="html">&lt;p&gt;My Mac menu bar apps, &lt;a href=&quot;https://www.hexedbits.com/redeye/&quot;&gt;Red Eye&lt;/a&gt; and &lt;a href=&quot;https://www.hexedbits.com/lucifer/&quot;&gt;Lucifer&lt;/a&gt;, are now for sale on my new &lt;a href=&quot;https://hexedbits.gumroad.com&quot;&gt;Gumroad store&lt;/a&gt;.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;My Mac menu bar apps, &lt;a href=&quot;https://www.hexedbits.com/redeye/&quot;&gt;Red Eye&lt;/a&gt; and &lt;a href=&quot;https://www.hexedbits.com/lucifer/&quot;&gt;Lucifer&lt;/a&gt;, are now for sale on my new &lt;a href=&quot;https://hexedbits.gumroad.com&quot;&gt;Gumroad store&lt;/a&gt;.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;Previously, these were free downloads on &lt;a href=&quot;https://www.hexedbits.com&quot;&gt;my apps website&lt;/a&gt;. If you currently have them installed, nothing will change for you. They will keep working. However, if you have ever been helped by &lt;a href=&quot;/blog/archive/&quot;&gt;one of my blog posts&lt;/a&gt;, enjoyed &lt;a href=&quot;/speaking/&quot;&gt;one of my conference talks&lt;/a&gt;, or used &lt;a href=&quot;https://github.com/jessesquires/&quot;&gt;one of my open source libraries&lt;/a&gt;, then buying these apps is a great way to support my work!&lt;/p&gt;

&lt;p&gt;I have listed them both for $2, but Gumroad allows you to pay more if you like — similar to artists selling albums on &lt;a href=&quot;https://bandcamp.com&quot;&gt;Bandcamp&lt;/a&gt;. I realize it’s a lot to ask you to pay for something that has been free until now, but your support is very much appreciated! And if you are able to leave a rating, that would also be great.&lt;/p&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;p&gt;You might be wondering, &lt;em&gt;why now?&lt;/em&gt; Well, I am trying to get more serious about monetization and experimenting with some different sales and distribution models &lt;em&gt;outside&lt;/em&gt; of the Mac App Store. Since &lt;a href=&quot;/blog/2023/04/10/going-indie/&quot;&gt;going independent&lt;/a&gt;, I have been financially supported primarily through freelance and contracting work. My goal is to start shifting more of my income towards my indie apps and away from freelance gigs. With these tiny menu bar apps on Gumroad, I’m dipping my toes in the water. Also, I find &lt;em&gt;selling something&lt;/em&gt; to be a bit more compelling than simple donations like &lt;a href=&quot;https://github.com/sponsors/jessesquires&quot;&gt;GitHub Sponsors&lt;/a&gt; as a way of supporting someone. (Although, if you would like to sponsor me on GitHub, that would be great too!)&lt;/p&gt;

&lt;p&gt;More importantly, I have another Mac app that I’m planning to release this year — &lt;em&gt;not on the Mac App Store&lt;/em&gt;. I likely &lt;em&gt;will not&lt;/em&gt; be selling and distributing that via Gumroad. I’m currently looking into &lt;a href=&quot;https://fastspring.com&quot;&gt;FastSpring&lt;/a&gt; and &lt;a href=&quot;https://www.paddle.com&quot;&gt;Paddle&lt;/a&gt; to determine which is a better fit. We’ll see how it all works out!&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/01/18/menu-bar-apps-for-sale/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/01/17/swift-protocol-requirement-quirks/</id>
        <link href="https://www.jessesquires.com/blog/2024/01/17/swift-protocol-requirement-quirks/" />
        <title>Swift protocol requirement quirks</title>
        <published>2024-01-17T10:40:34-08:00</published>
        <updated>2024-01-17T10:40:34-08:00</updated>

        <category term="software-dev" />
        <category term="swift" />
        <summary type="html">&lt;p&gt;Perhaps “quirks” is not the correct description, but I recently encountered some unexpected behavior when modifying a protocol in Swift. While I was initially slightly confused, how Swift handles protocol requirements does make sense — conformances are more lenient than you might think!&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;Perhaps “quirks” is not the correct description, but I recently encountered some unexpected behavior when modifying a protocol in Swift. While I was initially slightly confused, how Swift handles protocol requirements does make sense — conformances are more lenient than you might think!&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;I was updating one of my open source Swift packages, &lt;a href=&quot;https://github.com/jessesquires/foil&quot;&gt;Foil&lt;/a&gt;, which provides a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserDefaults&lt;/code&gt; property wrapper. Prior to &lt;a href=&quot;https://github.com/jessesquires/Foil/releases/tag/5.0.0&quot;&gt;the latest release&lt;/a&gt;, it defined the following protocol:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UserDefaultsSerializable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;associatedtype&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;StoredValue&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;storedValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;StoredValue&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;storedValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;StoredValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And I was changing the protocol to make the initializer &lt;a href=&quot;https://docs.swift.org/swift-book/documentation/the-swift-programming-language/initialization/#Failable-Initializers&quot;&gt;failable&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gd&quot;&gt;- init(storedValue: StoredValue)
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+ init?(storedValue: StoredValue)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I anticipated getting lots of compiler errors (or at least warnings) about conformers who declared &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init?&lt;/code&gt; — but that did not happen. It turns out, this is by design. In some situations, protocol witnesses (values or types that satisfy protocol requirements) do not have to exactly match a protocol requirement. Protocol witnesses can sometimes mismatch requirements with more “lenient” alternatives. You can see this in the example above: a non-failable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init&lt;/code&gt; still satisfies the protocol requirements when a failable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init?&lt;/code&gt; is specified. It might seem like a mistake at first, but this makes sense.&lt;/p&gt;

&lt;p&gt;Consider another scenario in Swift: a non-optional value can be passed to an optional parameter in a function or set to an optional property, but not the other way around — that is, you &lt;em&gt;cannot&lt;/em&gt; pass an &lt;em&gt;optional&lt;/em&gt; value to a function parameter that accepts a &lt;em&gt;non-optional&lt;/em&gt; value.&lt;/p&gt;

&lt;p&gt;Many thanks to &lt;a href=&quot;https://mastodon.social/@aligatr@ohai.social/111728768518347308&quot;&gt;Olivier Halligon&lt;/a&gt;, &lt;a href=&quot;https://mastodon.social/@jrose@belkadan.com/111739119102582991&quot;&gt;Jordan Rose&lt;/a&gt;, and &lt;a href=&quot;https://mastodon.social/@ole@chaos.social/111739130657945322&quot;&gt;Ole Begemann&lt;/a&gt; for the help in understanding this in our discussion on Mastodon. You can find a thorough discussion on the Swift Forums in this post, &lt;a href=&quot;https://forums.swift.org/t/protocol-witness-matching-roadmap/60297&quot;&gt;&lt;em&gt;Protocol Witness Matching Roadmap&lt;/em&gt;&lt;/a&gt;, started by Suyash Srijan.&lt;/p&gt;

&lt;p&gt;A handful of protocol witness mismatches are currently allowed:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Non-failable initializers can satisfy failable initializer protocol requirements (as I encountered above)&lt;/li&gt;
  &lt;li&gt;Non-throwing functions can satisfy throwing function protocol requirements&lt;/li&gt;
  &lt;li&gt;Non-escaping closure parameters can satisfy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@escaping&lt;/code&gt; protocol requirements&lt;/li&gt;
  &lt;li&gt;Generic functions can satisfy non-generic protocol requirements&lt;/li&gt;
  &lt;li&gt;Non-mutating functions can satisfy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mutating&lt;/code&gt; protocol requirements&lt;/li&gt;
  &lt;li&gt;Enum cases can satisfy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;static&lt;/code&gt; function protocol requirements&lt;/li&gt;
  &lt;li&gt;Synchronous methods can satisfy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; protocol requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most other protocol witness mismatches are forbidden and will produce a compiler error.&lt;/p&gt;

&lt;p&gt;An easy way to think about this is that the requirements above can be satisfied by &lt;em&gt;more specific&lt;/em&gt; or &lt;em&gt;more specialized&lt;/em&gt; declarations, but not &lt;em&gt;less specific&lt;/em&gt; or &lt;em&gt;less constrained&lt;/em&gt; declarations. A square is a rectangle, but a rectangle is not a square.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://forums.swift.org/t/protocol-witness-matching-roadmap/60297&quot;&gt;forum discussion&lt;/a&gt; outlines further details and potential plans for the future. I recommend reading it!&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/01/17/swift-protocol-requirement-quirks/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/01/16/app-store-screenshot-requirements/</id>
        <link href="https://www.jessesquires.com/blog/2024/01/16/app-store-screenshot-requirements/" />
        <title>App Store screenshot requirements need to change</title>
        <published>2024-01-16T09:52:29-08:00</published>
        <updated>2024-01-16T09:52:29-08:00</updated>

        <category term="software-dev" />
        <category term="app-store" /><category term="mac-app-store" /><category term="ios" /><category term="macos" /><category term="apple" /><category term="screenshots" />
        <summary type="html">&lt;p&gt;Providing screenshots for the App Store has always been a tedious and time-consuming process. But as the number of differently-sized iOS devices has grown and changed over the years, it has become more difficult to manage. (This is why the developer community built tools like &lt;a href=&quot;https://docs.fastlane.tools/actions/snapshot/&quot;&gt;fastlane snapshot&lt;/a&gt;.) The screenshot requirements for the App Store have increasingly become a burden for developers, especially indies. With the Mac App Store, there are fewer hurdles and less strict requirements. However, if you are now targeting only the latest OS releases and latest hardware, the screenshot requirements for both App Stores are not only burdensome but they no longer makes any sense!&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;Providing screenshots for the App Store has always been a tedious and time-consuming process. But as the number of differently-sized iOS devices has grown and changed over the years, it has become more difficult to manage. (This is why the developer community built tools like &lt;a href=&quot;https://docs.fastlane.tools/actions/snapshot/&quot;&gt;fastlane snapshot&lt;/a&gt;.) The screenshot requirements for the App Store have increasingly become a burden for developers, especially indies. With the Mac App Store, there are fewer hurdles and less strict requirements. However, if you are now targeting only the latest OS releases and latest hardware, the screenshot requirements for both App Stores are not only burdensome but they no longer makes any sense!&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;h3 id=&quot;a-brief-history-of-screen-sizes&quot;&gt;A (brief) history of screen sizes&lt;/h3&gt;

&lt;p&gt;For the first ~8 years of the iPhone, screen sizes and resolutions did not change. (Yes, the iPhone 5 got a &lt;em&gt;little bit&lt;/em&gt; taller, but it was not a dramatic change.) All devices had 3.5-inch or 4-inch displays. Similarly, iPad screen sizes and resolutions did not change for the first ~5 years. Each device has a history that can be broken up into distinct phases.&lt;/p&gt;

&lt;p&gt;The first phase of iPhone was the original &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;320 x 480&lt;/code&gt; display. The second phase and the first fundamental changes for iPhone came with iPhone 6 and iPhone 6 Plus (up to the 8 and 8 Plus), which had larger and higher resolution displays. These displays were 4.7-inch and 5.5-inch. The next fundamental shift occurred with iPhone X, which began the third (and current) phase. This includes the current lineup of iPhones 15, with the Plus, Pro, and Pro Max. Again, this phase brought larger screen sizes, higher resolution displays, and — importantly — different aspect ratios. The current generation of display sizes for iPhone include 5.8-inch, 6.1-inch, 6.5-inch, and 6.7-inch.&lt;/p&gt;

&lt;p&gt;The first fundamental changes for iPad occurred with iPad Pro and then again with iPad Air. Overall, iPad screen size changes have been more subtle and resolutions have been much more consistent. The majority of iPad models have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3:4&lt;/code&gt; aspect ratio, the outliers are the recent generations of iPad Air and iPad Mini. The current generation of display sizes for iPad include 8.3-inch, 9.7-inch, 10.5-inch, 11-inch, and 12.9-inch.&lt;/p&gt;

&lt;p&gt;If you want to browse through all the details, I highly recommend &lt;a href=&quot;https://www.screensizes.app&quot;&gt;ScreenSizes.app&lt;/a&gt; and &lt;a href=&quot;https://iosref.com/res&quot;&gt;iOSRef.com&lt;/a&gt;.&lt;/p&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;p&gt;On the Mac, the story is (expectedly) quite different. Obviously, the Mac is significantly older than iPhone and iPad and a significantly different machine. However, the &lt;em&gt;Mac App Store&lt;/em&gt; is about 2.5 years &lt;em&gt;younger&lt;/em&gt; than the iOS App Store. MacBooks have been pretty consistent in screen size and resolution. The first fundamental shift regarding resolution occurred with the Retina Displays. Over the years, there have been Mac laptops with displays ranging from 11-inches to 17-inches across the MacBook, MacBook Air, and MacBook Pro lineups. (Fun fact: with the introduction of the M-series MacBook Pro, there has been a MacBook display size for every 1-inch increment from 11 to 17.) Then there are the desktop Macs, some of which include a display like the iMac (ranging from 20-inch to 27-inch, which eventually shifted to Retina Displays) while the others require an external display. With external displays, there are too many possibilities to enumerate.&lt;/p&gt;

&lt;p&gt;Unfortunately, I have been unable to find comprehensive resources about Mac Models and their specs like &lt;a href=&quot;https://www.screensizes.app&quot;&gt;ScreenSizes.app&lt;/a&gt; for iOS devices. Apple does maintain a thorough &lt;a href=&quot;https://support.apple.com/specs&quot;&gt;Tech Specs&lt;/a&gt; page, but it is rather tedious to examine each product manually and individually.&lt;/p&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;p&gt;I should also briefly mention that the discussion above is only considering screenshots for a single localization. If you need to provide screenshots in multiple languages, this is even more difficult to manage. Again, shoutout to &lt;a href=&quot;https://docs.fastlane.tools/&quot;&gt;fastlane&lt;/a&gt; for helping with this on iOS.&lt;/p&gt;

&lt;h3 id=&quot;aspect-ratios&quot;&gt;Aspect ratios&lt;/h3&gt;

&lt;p&gt;The most significant metric that affects screenshots is not device size, but aspect ratio. The aspect ratio of a display is what can produce the most notable differences in the content that is displayed on screen.&lt;/p&gt;

&lt;p&gt;iPhones with a 5.8-inch screen and larger have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;19.5:9&lt;/code&gt; aspect ratio, while all other modern iPhones (5.5-inch and smaller) have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;16:9&lt;/code&gt; aspect ratio. The now-obsolete iPhones with a 3.5-inch screen have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3:2&lt;/code&gt; aspect ratio.&lt;/p&gt;

&lt;p&gt;Almost all iPads have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4:3&lt;/code&gt; aspect ratio. iPads with an 11-inch screen have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10:7&lt;/code&gt; aspect ratio and iPads with a 10.9-inch screen have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;23:16&lt;/code&gt; aspect ratio. The single outlier is the 6th Gen iPad Mini, which is the only model with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;6.1:4&lt;/code&gt; aspect ratio.&lt;/p&gt;

&lt;p&gt;Most Macs and Apple Displays have either a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;16:10&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;16:9&lt;/code&gt; aspect ratio. However, the current generation of M-series MacBooks (Air and Pro) have an obscure &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;16:10.35&lt;/code&gt; aspect ratio. So weird.&lt;/p&gt;

&lt;h3 id=&quot;app-store-screenshot-requirements-for-ios&quot;&gt;App Store screenshot requirements for iOS&lt;/h3&gt;

&lt;p&gt;You can currently upload screenshots for all device sizes, if you wish. There was a time, if I remember correctly, when Apple required screenshots for &lt;em&gt;all&lt;/em&gt; device sizes. However, this was eventually relaxed to include only some of the larger devices, which can then be scaled down to display for smaller devices with the same aspect ratio. You can find the current &lt;a href=&quot;https://developer.apple.com/help/app-store-connect/reference/screenshot-specifications&quot;&gt;screenshot specification reference here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screenshot requirements for iPhone:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Either 6.7-inch or 6.5-inch (the current generation of devices). Scaled versions of these will be used for 6.1-inch and 5.8-inch.&lt;/li&gt;
  &lt;li&gt;5.5-inch (iPhone 6s/7/8 Plus). Scaled versions of these will be used for 4.7-inch, 4-inch, and 3.5-inch.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, if your app is targeting iOS 17 and later, &lt;strong&gt;generating 5.5-inch screenshots is impossible!&lt;/strong&gt; There are &lt;a href=&quot;https://support.apple.com/guide/iphone/models-compatible-with-ios-17-iphe3fa5df43/17.0/ios/17.0&quot;&gt;no 5.5-inch devices that can run iOS 17&lt;/a&gt;! The iPhones 6s/7/8 Plus stopped at iOS 16. It doesn’t make any sense to provide these screenshots if your app only runs on iOS 17 and later.&lt;/p&gt;

&lt;p&gt;Even more ridiculous, App Store Connect still allows you to submit 3.5-inch screenshots. Not only are those devices obsolete, it is impossible to release an app today that even runs on those devices. Xcode prevents you from setting a minimum deployment target lower than iOS 12, which does not run on any 3.5-inch device!&lt;/p&gt;

&lt;p&gt;Luckily, for tvOS and watchOS (and now visionOS) you only need to choose a single size for all of your screenshots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screenshot requirements for iPad:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;12.9-inch (iPad Pro 6th Gen, Face ID).&lt;/li&gt;
  &lt;li&gt;12.9-inch (iPad Pro 2nd Gen, Touch ID).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All other iPad sizes (9.7-inch, 10.5-inch, 11-inch) will use scaled versions of these. Notably, there is no option to upload screenshots for iPad Mini (not the old 7.9-inch display, nor the latest 8.3-inch display). Talk about consistency. This is certainly less cumbersome, at least there is &lt;a href=&quot;https://support.apple.com/guide/ipad/models-compatible-with-ipados-17-ipad213a25b2/17.0/ipados/17.0&quot;&gt;a device of each size capable of running iOS 17&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But you might have noticed… these screenshots are exactly the same size, resolution, and aspect ratio — so why the duplication? The &lt;strong&gt;only difference&lt;/strong&gt; is whether or not the device has a &lt;em&gt;home button&lt;/em&gt; (with Touch ID) or a virtual &lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/layout&quot;&gt;&lt;em&gt;home indicator&lt;/em&gt;&lt;/a&gt; (with Face ID). Thus, &lt;em&gt;the only difference&lt;/em&gt; when generating these two sets of screenshots is whether or not the home indicator is present at the bottom of the screen. I understand these are fundamental differences in the hardware, but do we really need to provide both sets of screenshots just for a difference of a few pixels at the bottom of the screen?&lt;/p&gt;

&lt;p&gt;Is a home button versus a home indicator truly a meaningful distinction for screenshots? I don’t think so, especially considering that Apple provides an API that &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden&quot;&gt;allows your app to hide the home indicator&lt;/a&gt;! In this case, screenshots would be identical. I do not see a good justification for the extra work involved here to generate two sets of the same screenshots.&lt;/p&gt;

&lt;h3 id=&quot;suggested-improvements-for-ios-screenshots&quot;&gt;Suggested improvements for iOS screenshots&lt;/h3&gt;

&lt;p&gt;I suspect, similar to the iPad requirements, the primary reason that a 5.5-inch screenshots are required for iPhone — aside from the difference in aspect ratio — is that this is the largest device with a home button (Touch ID). All other sizes (5.8-inch and larger) are for devices with a &lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/layout&quot;&gt;home indicator&lt;/a&gt; (Face ID), that is, no home button.&lt;/p&gt;

&lt;p&gt;Here are the changes I would like to see:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Remove all obsolete device sizes from App Store Connect.&lt;/li&gt;
  &lt;li&gt;Do not require screenshots for devices that cannot run your app, based on its minimum deployment target.&lt;/li&gt;
  &lt;li&gt;Do not require screenshots solely to distinguish between &lt;em&gt;home button&lt;/em&gt; and &lt;em&gt;home indicator&lt;/em&gt; devices.&lt;/li&gt;
  &lt;li&gt;(Maybe?) Only require screenshots for additional device sizes when there is a significant difference in size and aspect ratio, otherwise use scaled versions of the screenshots for other devices.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With these changes, developers would only need to provide the following screenshots:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;iPhone 6.5-inch&lt;/li&gt;
  &lt;li&gt;iPhone 4.7-inch (optional)&lt;/li&gt;
  &lt;li&gt;iPad 12.9-inch&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s 2 sets of screenshots instead of 4, with 1 optional set. I prefer that iPhone 4.7-inch screenshots be optional so that you only need one set per device, but I can see a valid argument against that because they have different aspect ratios. But hopefully, iPhones with 4.7-inch screens will be obsoleted with iOS 18, which then addresses this concern.&lt;/p&gt;

&lt;h3 id=&quot;app-store-screenshot-requirements-for-macos&quot;&gt;App Store screenshot requirements for macOS&lt;/h3&gt;

&lt;p&gt;As I mentioned above, the screen sizes for macOS are much more nebulous considering the numerous possibilities of externals displays. Thus, App Store Connect requires screenshots in any of the following sizes:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1280 x 800&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1440 x 900&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2560 x 1600&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2880 x 1800&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Luckily, you only have to choose one.&lt;/p&gt;

&lt;p&gt;Notably, these all have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;16:10&lt;/code&gt; aspect ratio — and that’s the problem. There is only one modern Mac that has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;16:10&lt;/code&gt; aspect ratio and it’s the 13-inch MacBook Air M1. The other MacBook Air models and all MacBook Pro models have an obscure &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;16:10.35&lt;/code&gt; aspect ratio — &lt;em&gt;almost there&lt;/em&gt;, but just different enough to be unable to simply take a desktop screenshot and scale it down to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2880 x 1800&lt;/code&gt;. Instead, you’ll have to scale it down and crop it. Thanks &lt;a href=&quot;/blog/2023/12/16/macbook-notch-and-menu-bar-fixes/&quot;&gt;notch&lt;/a&gt;! The current iMac models along with the Studio Display and Pro XDR Display all have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;16:9&lt;/code&gt; aspect ratios — though perhaps this is a moot point considering 4K+ images are obviously too massive.&lt;/p&gt;

&lt;p&gt;You &lt;em&gt;could&lt;/em&gt; simply take screenshots of your app windows at one of the specified sizes, and that will certainly work for some use cases. But what about menu bar apps? Or apps with multiple windows? Generally, I think screenshots look much nicer when your Mac app is shown within the context of a full desktop screenshot.&lt;/p&gt;

&lt;p&gt;Looking at the apps that Apple ships via the Mac App Store, you’ll see both types of screenshots for all of them. For example, &lt;a href=&quot;https://apps.apple.com/us/app/pages/id409201541&quot;&gt;Pages for Mac&lt;/a&gt; only has screenshots of the app itself in fullscreen. On the other hand, the &lt;a href=&quot;https://apps.apple.com/us/app/apple-developer/id640199958&quot;&gt;Developer App&lt;/a&gt; shows the app window on a full Mac desktop. Both are valid approaches and both should be easy for developers to generate.&lt;/p&gt;

&lt;h3 id=&quot;suggested-improvements-for-macos-screenshots&quot;&gt;Suggested improvements for macOS screenshots&lt;/h3&gt;

&lt;p&gt;Allowing 4K or larger desktop screenshots is obviously not feasible given their size. However, I think the App Store should allow additional aspect ratios and sizes.&lt;/p&gt;

&lt;p&gt;Here are the changes I would like to see:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Allow &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;16:9&lt;/code&gt; aspect ratios.&lt;/li&gt;
  &lt;li&gt;Allow the weird &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;16:10.35&lt;/code&gt; aspect ratios for the latest MacBook lineup.&lt;/li&gt;
  &lt;li&gt;Allow &lt;em&gt;at least&lt;/em&gt; one of the following new sizes:
    &lt;ol&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2560 x 1664&lt;/code&gt; (MacBook Air 13” M2, at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;16:10.35&lt;/code&gt;)&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3024 x 1964&lt;/code&gt; (MacBook Pro 14”, at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;16:10.35&lt;/code&gt;)&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This would make it significantly easier in many scenarios to generate screenshots without having scale and/or crop everything.&lt;/p&gt;

&lt;h3 id=&quot;current-solutions-and-workarounds&quot;&gt;Current solutions and workarounds&lt;/h3&gt;

&lt;p&gt;I’ve alluded to solutions above, but I’ll enumerate them here for clarity.&lt;/p&gt;

&lt;p&gt;For iOS, you can automate generating all screenshots with &lt;a href=&quot;https://docs.fastlane.tools/actions/snapshot/&quot;&gt;fastlane snapshot&lt;/a&gt;, which alleviates the majority of these issues. The tool is somewhat clunky and definitely feels like it is aging, but it still works. It would be nice if third-party tools like this were not necessary.&lt;/p&gt;

&lt;p&gt;To address having to provide 5.5-inch screenshots for an app that only runs on iOS 17 and later, there are a few steps and caveats. You need to satisfy the nonsensical requirement by uploading 5.5-inch screenshots, but you should also provide proper 4.7-inch screenshots, which display on devices that &lt;em&gt;can&lt;/em&gt; run your app. Providing both is important, as &lt;a href=&quot;https://lapcatsoftware.com/articles/2023/12/3.html&quot;&gt;Jeff Johnson has pointed out&lt;/a&gt;, if you only provide 5.5-inch screenshots they will be used for the 4.7-inch screen sizes. This is not ideal, because we have to manually resize them, which doesn’t look great. Here’s my process:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;I generate 4.7-inch screenshots using fastlane.&lt;/li&gt;
  &lt;li&gt;I upscale these screenshots to the 5.5-inch resolution using &lt;a href=&quot;https://flyingmeat.com/retrobatch/&quot;&gt;Retrobatch&lt;/a&gt;. (You could also use Photoshop, Sketch, Gimp, Acorn, or any other image editor.)&lt;/li&gt;
  &lt;li&gt;Upload the (somewhat ugly) scaled 5.5-inch screenshots along with all the others (via fastlane).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Technically&lt;/em&gt; (again, &lt;a href=&quot;https://lapcatsoftware.com/articles/2023/12/3.html&quot;&gt;Jeff notes&lt;/a&gt;) you only need to provide one 5.5-inch screenshot. Users will never see these because they’ll instead see the correct 4.7-inch screenshots. However, my automation with Retrobatch and fastlane make it trivial, so I do upload a full set for 5.5-inch screens.&lt;/p&gt;

&lt;p&gt;For macOS screenshots, you can choose to only upload screenshots of your app windows. If you have an M-series MacBook and want to take full desktop screenshots, you’ll have to resize and crop them to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2880 x 1800&lt;/code&gt;. &lt;a href=&quot;https://flyingmeat.com/retrobatch/&quot;&gt;Retrobatch&lt;/a&gt; can streamline this process, too. Unfortunately, &lt;a href=&quot;https://docs.fastlane.tools/actions/snapshot/&quot;&gt;fastlane snapshot&lt;/a&gt; remains broken for macOS.&lt;/p&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;p&gt;Finally, I should note that all of this only applies if you are taking proper screenshots. Unfortunately, the majority of listings in the App Store these days simply use various promotional images (sized appropriately) that were made in Photoshop, etc. But even if this is your strategy, generating screenshots in all the various sizes is still a burden. Being able to provide only a single set of screenshots per device would help.&lt;/p&gt;

&lt;h3 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h3&gt;

&lt;p&gt;I would love to see these changes (and fixes!) come to App Store Connect that make dealing with screenshots easier. Hopefully someone at Apple is listening!&lt;/p&gt;

&lt;h4 id=&quot;references&quot;&gt;References&lt;/h4&gt;

&lt;p&gt;Here are all the resources I used to find and confirm device and screenshot specifications:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/help/app-store-connect/reference/screenshot-specifications&quot;&gt;Apple - Screenshot specifications&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.screensizes.app&quot;&gt;ScreenSizes.app&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://iosref.com&quot;&gt;iOSRef.com&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://support.apple.com/guide/iphone/models-compatible-with-ios-17-iphe3fa5df43/17.0/ios/17.0&quot;&gt;Apple Support - iPhone model compatibility&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://support.apple.com/guide/ipad/models-compatible-with-ipados-17-ipad213a25b2/17.0/ipados/17.0&quot;&gt;Apple Support - iPad model compatibility&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/layout&quot;&gt;Apple Human Interface Guidelines - Layout&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://support.apple.com/specs&quot;&gt;Apple Support - Technical Specifications&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/01/16/app-store-screenshot-requirements/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/01/04/simctl-status_bar-still-broken/</id>
        <link href="https://www.jessesquires.com/blog/2024/01/04/simctl-status_bar-still-broken/" />
        <title>Workaround: Xcode simctl status_bar is still broken for iOS 17 simulators</title>
        <published>2024-01-04T12:50:38-08:00</published>
        <updated>2024-01-04T12:50:38-08:00</updated>

        <category term="software-dev" />
        <category term="series-perfect-status-bars" /><category term="ios" /><category term="xcode" />
        <summary type="html">&lt;p&gt;While working on updating iOS screenshots for the App Store recently, I discovered that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simctl status_bar&lt;/code&gt; is &lt;a href=&quot;/blog/2022/12/14/simctrl-status_bar-broken/&quot;&gt;&lt;strong&gt;still broken&lt;/strong&gt;&lt;/a&gt;. And unfortunately, I do not expect it to be fixed any time soon.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;While working on updating iOS screenshots for the App Store recently, I discovered that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simctl status_bar&lt;/code&gt; is &lt;a href=&quot;/blog/2022/12/14/simctrl-status_bar-broken/&quot;&gt;&lt;strong&gt;still broken&lt;/strong&gt;&lt;/a&gt;. And unfortunately, I do not expect it to be fixed any time soon.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;I’ve written before about &lt;a href=&quot;/blog/2020/04/13/fully-automating-perfect-status-bar-overrides-for-ios-simulators/&quot;&gt;automating perfect status bar overrides&lt;/a&gt; for the iOS simulator using my utility, &lt;a href=&quot;https://github.com/jessesquires/Nine41&quot;&gt;Nine41&lt;/a&gt;. The current bug in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simctl status_bar&lt;/code&gt; not only prevents Nine41 from working, but also breaks every third-party tool like &lt;a href=&quot;https://fastlane.tools&quot;&gt;fastlane&lt;/a&gt;, &lt;a href=&quot;https://simgenie.app&quot;&gt;SimGenie&lt;/a&gt;, etc. that offer a status bar override feature — because those all use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simctl status_bar&lt;/code&gt; under-the-hood.&lt;/p&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;/blog/2022/12/14/simctrl-status_bar-broken/&quot;&gt;Previously&lt;/a&gt;, I realized you could still use the iOS 16.0 simulators and earlier. However, if your app is targeting iOS 17 and above — my current situation since dropping iOS 16 — then this workaround will no longer suffice because your app no longer runs on iOS 16.&lt;/p&gt;

&lt;p&gt;The only workaround I have found is to use our good old friend, &lt;a href=&quot;https://github.com/shinydevelopment/SimulatorStatusMagic&quot;&gt;SimulatorStatusMagic&lt;/a&gt; — which &lt;a href=&quot;https://github.com/daveverwer&quot;&gt;Dave wrote&lt;/a&gt; &lt;em&gt;years ago&lt;/em&gt; before &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simctl status_bar&lt;/code&gt; existed. (It is now maintained by &lt;a href=&quot;https://github.com/shinydevelopment/SimulatorStatusMagic/graphs/contributors&quot;&gt;other great folks&lt;/a&gt;.) In my earlier post, I tried to guess (incorrectly) what might have caused the breakage in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simctl status_bar&lt;/code&gt;, but the project’s &lt;a href=&quot;https://github.com/shinydevelopment/SimulatorStatusMagic/blob/master/README.md&quot;&gt;README&lt;/a&gt; explains the problem:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;1) Injecting into Springboard (Required on iOS 17+)&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; Running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build_and_inject.sh booted&lt;/code&gt; will apply a default status bar to the running simulator. Replace “booted” with a simulator UDID to target a specific simulator.&lt;/p&gt;

  &lt;p&gt;As of iOS 17, the API used by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SimulatorStatusMagic&lt;/code&gt; is not accessible to processes other than Springboard. So, in iOS 17+ we need to inject &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SimulatorStatusMagic&lt;/code&gt; into the Springboard process itself, which we do by building it as a dynamic library, and then updating Springboard’s launchd configuration to load our dynamic library.&lt;/p&gt;

  &lt;p&gt;Running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build_and_inject.sh&lt;/code&gt; will do all of this for you. If you want to change anything about the values used in the status bar, you will need to update &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DynamicLibrary/main.m&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And there’s our answer: &lt;em&gt;“As of iOS 17, the API used by SimulatorStatusMagic is not accessible to processes other than Springboard.”&lt;/em&gt; Presumably, this is why &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simctl status_bar&lt;/code&gt; no longer works. From the outside, it sometimes feels like teams within Apple have little to no communication with each other, resulting in bugs like this. Or, perhaps this is just the typical dysfunction that manifests with very large organizations.&lt;/p&gt;

&lt;h4 class=&quot;text-center text-light-dark&quot;&gt;* * *&lt;/h4&gt;

&lt;p&gt;Luckily, the status bar overrides applied using SimulatorStatusMagic &lt;strong&gt;will persist across simulator launches&lt;/strong&gt;. Thus, you only need to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build_and_inject.sh booted&lt;/code&gt; once for each simulator. I decided to just do this for all of my simulators so that I never have to worry about this. (Honestly, there’s really no reason for them to ever show the current date and time.)&lt;/p&gt;

&lt;p&gt;If you fully reset a simulator using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Erase All Content and Settings...&lt;/code&gt;, then you will need to re-run the script for that simulator. Once you apply the overrides to the simulators, you can use whatever screenshot automation tools you prefer (Xcode UITests, fastlane snapshot, etc.) and the status bar overrides will remain. If you need this functionality on a CI service, you are in for a bit more work, but it is possible.&lt;/p&gt;

&lt;p&gt;This is far from ideal, but at least we have a workaround that allows us to continue creating great screenshots.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/01/04/simctl-status_bar-still-broken/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2024/01/03/app-store-rejection/</id>
        <link href="https://www.jessesquires.com/blog/2024/01/03/app-store-rejection/" />
        <title>Another frivolous and frustrating App Store rejection</title>
        <published>2024-01-03T18:34:44-08:00</published>
        <updated>2024-01-03T18:34:44-08:00</updated>

        <category term="software-dev" />
        <category term="ios" /><category term="macos" /><category term="apple" /><category term="app-store" /><category term="mac-app-store" /><category term="app-store-rejection" />
        <summary type="html">&lt;p&gt;I’m happy to share that I released &lt;a href=&quot;https://www.hexedbits.com/news/2024/01/03/taxatio-update/&quot;&gt;an update to Taxatio&lt;/a&gt; today, but unfortunately it was not without a lot of friction and hassle with the App Store approval process.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;I’m happy to share that I released &lt;a href=&quot;https://www.hexedbits.com/news/2024/01/03/taxatio-update/&quot;&gt;an update to Taxatio&lt;/a&gt; today, but unfortunately it was not without a lot of friction and hassle with the App Store approval process.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;&lt;a href=&quot;https://www.hexedbits.com/taxatio/&quot;&gt;Taxatio&lt;/a&gt; is a universal app for iOS and macOS. I submitted the same update (version 1.3.0) for both platforms on December 30. The iOS app was accepted within a few hours, which was excellent. However, the macOS update was delayed for a ridiculous reason, after waiting to be reviewed for three entire days.&lt;/p&gt;

&lt;p&gt;It is important to note — and I cannot stress this enough — the iOS app and macOS app are &lt;strong&gt;nearly identical&lt;/strong&gt;. They are SwiftUI apps with the exact same functionality and they share 90 percent of their code. The metadata in the App Store for each is exactly the same, the only exception is the screenshots. So, it was curious to me how one could be approved within hours while the other was waiting to be reviewed for multiple days — ultimately, only to be rejected.&lt;/p&gt;

&lt;p&gt;So what happened? On January 2, three days after submitting to App Review and three days after the iOS app was approved, I received this message from App Store Review (yes, with this formatting and typos):&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Bug Fix Submissions&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;The issues we’ve identified below are eligible to be resolved on your next update. If this submission includes bug fixes and you’d like to have it approved at this time, reply to this message and let us know. You do not need to resubmit your app for us to proceed.&lt;/p&gt;

  &lt;p&gt;Alternatively, if you’d like to resolve these issues now, please review the details, make the appropriate changes, and resubmit.&lt;/p&gt;

  &lt;h3 id=&quot;guideline-23---performance---accurate-metadata&quot;&gt;Guideline 2.3 - Performance - Accurate Metadata&lt;/h3&gt;

  &lt;p&gt;We noticed that your app’s metadata includes the following information, which is not relevant to the app’s content and functionality:&lt;/p&gt;

  &lt;p&gt;What’ new text showed iOS reference.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Next Steps&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;To resolve this issue, please revise or remove this content from your app’s metadata. For resources on metadata best practices, you may want to review the App Store Product Page information available on the Apple Developer website.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my release notes for this update &lt;strong&gt;for both the iOS app and the macOS app&lt;/strong&gt;, I wrote:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;Dropped support for iOS 16 and macOS 13. Now requires minimum iOS 17 and macOS 14.&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;So acknowledging that my app is a universal app — a fact that users are aware of — is grounds for a &lt;strong&gt;rejection&lt;/strong&gt;? That is beyond ridiculous, especially considering &lt;strong&gt;the iOS app contained the exact same release notes&lt;/strong&gt; and got approved. I mean, seriously! What a fucking joke! Not to mention, Apple &lt;em&gt;actively encourages&lt;/em&gt; developers to &lt;a href=&quot;https://developer.apple.com/support/universal-purchase/&quot;&gt;support universal purchases&lt;/a&gt; for apps available on their platforms since they started &lt;a href=&quot;https://developer.apple.com/news/?id=02052020a&quot;&gt;supporting this in 2020&lt;/a&gt;. I do not think that mentioning OS support changes for a universal app is “not relevant” and I certainly do not think it should result in a rejection. It’s harmless.&lt;/p&gt;

&lt;p&gt;I replied asking to be approved anyway and “resolve” the “issues” on my next update — but surprise, my release notes for the next update will be completely different anyway. I replied shortly after receiving the rejection notice, hoping that the turn around would be quick — I had already waited three days! Of course, it took another entire day to finally be approved on January 3. That means time from submission to approval was four days in total.&lt;/p&gt;

&lt;p&gt;I have two major complaints about this process.&lt;/p&gt;

&lt;p&gt;First, the inconsistency is absolutely maddening. The time discrepancy between review times between platforms is annoying, especially considering you can submit multiple items for review for a universal app at once — and the fact that, you know, &lt;strong&gt;the app is universal&lt;/strong&gt;. Wouldn’t it make sense to review and approve both at the same time? More obviously and more infuriating is the fact that these apps are identical with identical release notes. There were no issues with the iOS app release notes mentioning macOS, but apparently you cannot mention iOS in the release notes for a macOS app.&lt;/p&gt;

&lt;p&gt;Second, while I appreciate Apple’s new approach to allowing apps to be approved for minor “violations” without having to resubmit, it does not work well in practice (obviously). In 2020, &lt;a href=&quot;https://www.apple.com/newsroom/2020/06/apple-reveals-new-developer-technologies-to-foster-the-next-generation-of-apps/&quot;&gt;Apple announced&lt;/a&gt; &lt;em&gt;“for apps that are already on the App Store, &lt;strong&gt;bug fixes will no longer be delayed&lt;/strong&gt; over guideline violations except for those related to legal issues. Developers will instead be able to address the issue in their next submission.”&lt;/em&gt; (Emphasis mine.) This sounded great in 2020, but the problem is that my app release &lt;em&gt;was still delayed&lt;/em&gt;. I’m glad I did not have to wait &lt;em&gt;another three days&lt;/em&gt;, but I did have to wait &lt;em&gt;another entire day&lt;/em&gt; because of this inconsistent, bureaucratic nonsense. Instead of the back-and-forth, why not simply approve the submission immediately and then mandate changes on the next submission?&lt;/p&gt;

&lt;p&gt;It is so disappointing, after so many years, that the App Store approval process continues to be so erratic, unpredictable, petty, and (often) slow for such benign app updates — all the while &lt;a href=&quot;https://www.theverge.com/2021/4/21/22385859/apple-app-store-scams-fraud-review-enforcement-top-grossing-kosta-eleftheriou&quot;&gt;scam apps continue to get approved&lt;/a&gt; and promoted in &lt;a href=&quot;https://9to5mac.com/2021/04/12/fake-reviews-and-ratings-manipulation-app-store/&quot;&gt;the top charts&lt;/a&gt;, much less &lt;a href=&quot;https://mjtsai.com/blog/2022/07/14/most-fraudulent-apps-still-on-the-app-store/&quot;&gt;get rejected&lt;/a&gt;.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2024/01/03/app-store-rejection/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2023/12/29/reading-list-2023/</id>
        <link href="https://www.jessesquires.com/blog/2023/12/29/reading-list-2023/" />
        <title>A list of books I read in 2023</title>
        <published>2023-12-29T12:02:14-08:00</published>
        <updated>2023-12-29T12:02:14-08:00</updated>

        <category term="reading-notes" />
        <category term="books" /><category term="reading-list" />
        <summary type="html">&lt;p&gt;Continuing another tradition, here are the books I read in 2023. Similar to my &lt;a href=&quot;/blog/2023/12/29/top-posts-of-2023/&quot;&gt;previous post&lt;/a&gt; highlighting my top posts of 2023, I also skipped publishing my reading list last year, for 2022. Again, I was simply too burnt out by the end of that year. I also did not read that much compared to previous years — thanks again to burnout. You can find previous years &lt;a href=&quot;/blog/tags/reading-list/&quot;&gt;here under the #reading-list tag&lt;/a&gt;.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;Continuing another tradition, here are the books I read in 2023. Similar to my &lt;a href=&quot;/blog/2023/12/29/top-posts-of-2023/&quot;&gt;previous post&lt;/a&gt; highlighting my top posts of 2023, I also skipped publishing my reading list last year, for 2022. Again, I was simply too burnt out by the end of that year. I also did not read that much compared to previous years — thanks again to burnout. You can find previous years &lt;a href=&quot;/blog/tags/reading-list/&quot;&gt;here under the #reading-list tag&lt;/a&gt;.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;Since I skipped last year, I’ll include everything for 2022 and 2023 in this list. As usual, these are in no particular order but I’ve grouped books by the same author together.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;The Will to Change: Men, Masculinity, and Love&lt;/em&gt;, by bell hooks&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Bone Black&lt;/em&gt;, by bell hooks&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;All About Love: New Visions&lt;/em&gt;, by bell hooks&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Night Sky with Exit Wounds&lt;/em&gt;, by Ocean Vuong&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Time Is a Mother&lt;/em&gt;, by Ocean Vuong&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Happy-Go-Lucky&lt;/em&gt;, by David Sedaris&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;The Hidden Life of Trees&lt;/em&gt;, by Peter Wohlleben&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;War and Peace in Kurdistan&lt;/em&gt;, by Abdullah Öcalan&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Liberating Life: Woman’s Revolution&lt;/em&gt;, by Abdullah Öcalan&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Arabian Love Poems&lt;/em&gt;, by Nizar Qabbani&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Salt Houses&lt;/em&gt;, by Hala Alyan&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;How to Be an Antiracist&lt;/em&gt;, by Ibram X. Kendi&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hopefully, I can get back into a better reading routine for 2024.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2023/12/29/reading-list-2023/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2023/12/29/top-posts-of-2023/</id>
        <link href="https://www.jessesquires.com/blog/2023/12/29/top-posts-of-2023/" />
        <title>Top posts of 2023</title>
        <published>2023-12-29T10:33:40-08:00</published>
        <updated>2023-12-29T10:33:40-08:00</updated>

        <category term="essays" />
        <category term="top-posts" />
        <summary type="html">&lt;p&gt;To continue my (almost) tradition of sharing my top posts, here are my most popular posts of 2023. You can find previous years &lt;a href=&quot;/blog/tags/top-posts/&quot;&gt;here under the #top-posts tag&lt;/a&gt;. Last year, 2022, is notably absent from this series — I was too burnt out last year (for a number of reasons) to write one of these posts. So as a bonus, I’ll include my top posts of 2022 here as well! As usual, all of my analytics data is &lt;a href=&quot;https://stats.jessesquires.com&quot;&gt;publicly available&lt;/a&gt;, made possible by the excellent &lt;a href=&quot;https://www.goatcounter.com&quot;&gt;GoatCounter Analytics&lt;/a&gt;, so you can view it too.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;To continue my (almost) tradition of sharing my top posts, here are my most popular posts of 2023. You can find previous years &lt;a href=&quot;/blog/tags/top-posts/&quot;&gt;here under the #top-posts tag&lt;/a&gt;. Last year, 2022, is notably absent from this series — I was too burnt out last year (for a number of reasons) to write one of these posts. So as a bonus, I’ll include my top posts of 2022 here as well! As usual, all of my analytics data is &lt;a href=&quot;https://stats.jessesquires.com&quot;&gt;publicly available&lt;/a&gt;, made possible by the excellent &lt;a href=&quot;https://www.goatcounter.com&quot;&gt;GoatCounter Analytics&lt;/a&gt;, so you can view it too.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;I would like thank everyone who continues to read my blog. I truly appreciate being able to share my writing with all of you.&lt;/p&gt;

&lt;h4 id=&quot;most-popular-posts-written-in-2023&quot;&gt;Most popular posts written in 2023&lt;/h4&gt;

&lt;p&gt;You can view &lt;a href=&quot;https://stats.jessesquires.com/?period-start=2023-01-01&amp;amp;period-end=2023-12-31&amp;amp;filter=%2Fblog%2F2023&quot;&gt;the data here&lt;/a&gt; for this year’s top posts, which is filtered to display site visits for all of 2023 and only for posts written in 2023 (that is, URLs matching &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/blog/2023/&lt;/code&gt;). To see everything I’ve written in 2023, you can &lt;a href=&quot;/blog/archive/&quot;&gt;browse the archive&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/03/02/icloud-tabs-bug/&quot;&gt;How to fix iCloud Safari tabs syncing bug&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/03/23/improve-multiplatform-swiftui-code/&quot;&gt;Improving multiplatform SwiftUI code&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/07/17/stop-prefixing-userdefaults-keys/&quot;&gt;Stop prefixing your UserDefaults keys&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/03/02/xcode-tip-filter-console/&quot;&gt;Xcode Tip: filtering debugger output&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/03/22/xcode-tip-filter-modified-files/&quot;&gt;Xcode Tip: filter to show modified files only&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/02/20/ios-view-controller-loading/&quot;&gt;How to find and fix premature view controller loading on iOS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/08/17/swift-url-absolutestring-path/&quot;&gt;Swift URL absoluteString versus path&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/01/18/eventmachine-failure-on-macos-ventura/&quot;&gt;Fix: eventmachine gem failed to build on macOS Ventura with Ruby 2.7.6&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/04/04/exploring-a-new-ios-codebase/&quot;&gt;Exploring a new iOS codebase&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/07/11/where-are-xcode-bookmarks-stored/&quot;&gt;Where are Xcode bookmarks stored?&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;most-popular-posts-written-in-2022&quot;&gt;Most popular posts written in 2022&lt;/h4&gt;

&lt;p&gt;Similarly to 2023, you can view the &lt;a href=&quot;https://stats.jessesquires.com/?period-start=2022-01-01&amp;amp;period-end=2022-12-31&amp;amp;filter=%2Fblog%2F2022&quot;&gt;2022 data here&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2022/04/19/github-suspending-russian-accounts/&quot;&gt;GitHub suspending Russian accounts deleted project history and pull requests&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2022/02/01/decimal-vs-double/&quot;&gt;When should you use Decimal instead of Double?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2022/03/26/gh-action-merge-release-to-main/&quot;&gt;Automate merging release branches into your main branch with GitHub Actions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2022/01/11/time-machine-error-35-monterey/&quot;&gt;Time Machine error 35 in macOS Monterey&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2022/08/11/implementing-a-main-thread-watchdog-on-ios/&quot;&gt;Implementing a main thread watchdog on iOS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2022/01/26/core-data-optionals/&quot;&gt;How to more gracefully handle non-optional Core Data properties in Swift&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2022/03/25/my-website-disappeared-from-bing-and-duckduckgo/&quot;&gt;My website disappeared from Bing and DuckDuckGo&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2022/01/11/on-third-party-apple-watch-apps/&quot;&gt;On third-party Apple Watch apps&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2022/02/15/mac-sigh-of-death/&quot;&gt;The MacBook sigh of death&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2022/01/11/ios-app-library-is-drunk/&quot;&gt;iOS App Library is drunk&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2023/12/29/top-posts-of-2023/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2023/12/16/macbook-notch-and-menu-bar-fixes/</id>
        <link href="https://www.jessesquires.com/blog/2023/12/16/macbook-notch-and-menu-bar-fixes/" />
        <title>How to fix Mac menu bar icons hidden by the MacBook notch</title>
        <published>2023-12-16T10:48:15-08:00</published>
        <updated>2024-05-29T18:20:52-07:00</updated>

        <category term="essays" />
        <category term="apple" /><category term="macbook-pro" /><category term="macbook" /><category term="macos" />
        <summary type="html">&lt;p&gt;Last week I &lt;a href=&quot;/blog/2023/12/04/new-m3-mbp/&quot;&gt;wrote about setting up a new MacBook Pro&lt;/a&gt; — my first Apple Silicon Mac, and thus my first MacBook with a notch. I lamented how poorly macOS interacts with the notch, specifically how menu bar apps and icons simply get hidden if you have too many to display. Lots of folks on Mastodon offered various solutions, and some readers emailed me with options as well. I figured it was worth making a separate post about this specific issue to list all of the workarounds and alternatives. It is clear that this is a widespread problem that users are having.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;Last week I &lt;a href=&quot;/blog/2023/12/04/new-m3-mbp/&quot;&gt;wrote about setting up a new MacBook Pro&lt;/a&gt; — my first Apple Silicon Mac, and thus my first MacBook with a notch. I lamented how poorly macOS interacts with the notch, specifically how menu bar apps and icons simply get hidden if you have too many to display. Lots of folks on Mastodon offered various solutions, and some readers emailed me with options as well. I figured it was worth making a separate post about this specific issue to list all of the workarounds and alternatives. It is clear that this is a widespread problem that users are having.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;h3 id=&quot;problems-with-the-notch&quot;&gt;Problems with the notch&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;/blog/2023/12/04/new-m3-mbp/&quot;&gt;I previously wrote&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I have gripes about the notch. There isn’t enough room to display all of my menu bar apps and icons, so… they just get hidden!? Apparently, everyone in Cupertino thinks the best solution to this problem is to hide them with zero indication that there are more that simply can’t be displayed because of the notch. I wasted so much time trying to figure out why Little Snitch and 1Password were not running on my new machine. Was there a compatibility issue with Apple Silicon that I didn’t know about? That couldn’t be. It turns out, they were running the whole time but they were hidden by the notch.&lt;/p&gt;

  &lt;p&gt;[…]&lt;/p&gt;

  &lt;p&gt;This “design” (or lack thereof) is so dumb. It is utterly ridiculous to me that this is still how it “works” &lt;strong&gt;two years after&lt;/strong&gt; the introduction of the redesigned MacBook Pro with a notch. How hard could it be to add an overflow menu with a “«” (or should it be “»”?) button that shows the remaining apps and icons that can’t be displayed? This entire situation with the notch is ironic, because the iPhone notch and “dynamic island” are so &lt;strong&gt;thoughtfully designed&lt;/strong&gt; with zero compromises regarding the functionality of iOS. In fact, they actually provide a &lt;em&gt;better&lt;/em&gt; user experience. Yet on the Mac, how the notch interacts with macOS is laughably incompetent. It is shockingly lazy regarding attention to detail, and results in an outright disruptive and confusing user experience.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And as &lt;a href=&quot;https://mjtsai.com/blog/2023/12/08/mac-menu-bar-icons-and-the-notch/&quot;&gt;Michael Tsai pointed out&lt;/a&gt;, the situation from a developer’s perspective is just as bad:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Aside from the problem of the icons being hidden, there’s no API for an app to &lt;em&gt;tell&lt;/em&gt; whether its icon is hidden. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSStatusItem.isVisible&lt;/code&gt; tells you whether the app or user &lt;em&gt;wants&lt;/em&gt; the icon to be visible, but it will return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt; if the icon is hidden in the notch—or even if it’s hidden behind a menu title.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;first-party-workarounds&quot;&gt;First-party workarounds&lt;/h3&gt;

&lt;p&gt;If you prefer &lt;em&gt;not&lt;/em&gt; to install any third-party apps (like me), there are a number of steps you can take to alleviate your crowded menu bar and possibly remedy the issue entirely (which I’ve successfully done). My goal with the steps below is to get all of my apps and icons to display when Finder is active. Some applications have more menu items that span across the other side of the notch, and in this scenario there is not much we can do to prevent hidden menu bar icons — which was actually the case &lt;em&gt;before&lt;/em&gt; the notch existed for applications with a very large number of menu items.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Move macOS system-provided icons into Control Center. A number of icons can be configured to display only in Control Center, which frees up space in your menu bar for icons and third-party apps that must be displayed in the menu bar. For example, you can move WiFi, Bluetooth, Battery level, AirDrop, etc. into Control Center. Additionally, some icons can be configured to only display when they are active, like Focus status or Screen Mirroring.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Reorder your apps and icons from right to left to display the ones that are &lt;em&gt;most important&lt;/em&gt; to you on the right and least important on the left. The result is that the least important icons are the ones that get hidden by application menus or the notch, while the most important icons are more likely to remain visible. For example, I like having Time Machine in the menu bar, but it doesn’t matter if it gets hidden while I’m working in an app with a large menu that spans to the other side of the notch, thus hiding Time Machine. Therefore, I have placed Time Machine in the left-most position. Furthermore, I place system icons that display &lt;em&gt;only when active&lt;/em&gt; (like Focus status) in the &lt;em&gt;right most&lt;/em&gt; position. This is important for me, because I want to make sure I always see when these things are active.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Reduce the menu bar item spacing and padding via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserDefaults&lt;/code&gt;. (Thanks to &lt;a href=&quot;https://mastodon.social/@gummibando/111546699397435187&quot;&gt;Oliver Busch&lt;/a&gt; for the tip. Also see &lt;a href=&quot;https://www.reddit.com/r/MacOS/comments/16lpfg5/hidden_preference_to_alter_the_menubar_spacing/&quot;&gt;this Reddit post&lt;/a&gt;.) There are two defaults settings you can configure via Terminal, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSStatusItemSpacing&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSStatusItemSelectionPadding&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Read the current defaults:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;defaults &lt;span class=&quot;nt&quot;&gt;-currentHost&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-globalDomain&lt;/span&gt; NSStatusItemSpacing
defaults &lt;span class=&quot;nt&quot;&gt;-currentHost&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-globalDomain&lt;/span&gt; NSStatusItemSelectionPadding
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; These values are &lt;em&gt;not set&lt;/em&gt; by default. This means you will get an error that the keys and values do not exist if you have not previously set them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write the defaults by providing an integer value:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;defaults &lt;span class=&quot;nt&quot;&gt;-currentHost&lt;/span&gt; write &lt;span class=&quot;nt&quot;&gt;-globalDomain&lt;/span&gt; NSStatusItemSpacing &lt;span class=&quot;nt&quot;&gt;-int&lt;/span&gt; 12
defaults &lt;span class=&quot;nt&quot;&gt;-currentHost&lt;/span&gt; write &lt;span class=&quot;nt&quot;&gt;-globalDomain&lt;/span&gt; NSStatusItemSelectionPadding &lt;span class=&quot;nt&quot;&gt;-int&lt;/span&gt; 8
killall SystemUIServer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After some experimentation, I landed on the values above — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;12&lt;/code&gt; for spacing and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8&lt;/code&gt; for padding fit my needs. You should experiment as well. The smallest tolerable values are probably around &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;6&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remove the values to restore the default behavior:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;defaults &lt;span class=&quot;nt&quot;&gt;-currentHost&lt;/span&gt; delete &lt;span class=&quot;nt&quot;&gt;-globalDomain&lt;/span&gt; NSStatusItemSpacing
defaults &lt;span class=&quot;nt&quot;&gt;-currentHost&lt;/span&gt; delete &lt;span class=&quot;nt&quot;&gt;-globalDomain&lt;/span&gt; NSStatusItemSelectionPadding
killall SystemUIServer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;third-party-solutions&quot;&gt;Third-party solutions&lt;/h3&gt;

&lt;p&gt;There are several third-party applications to help organize and manage all of your menu bar apps and icons. The apps I have listed below seem to capture the three broad categories of possible solutions. I know there are a few others out there with similar functionality, but I think these are the most representative.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Bartender&lt;/strong&gt;. (Paid with Free Trial. &lt;a href=&quot;https://www.macbartender.com&quot;&gt;Website&lt;/a&gt;). This one is very advanced and complex with elaborate customization options, including styling the entire menu bar. It works well, but I found it to be very heavy-handed and a bit cumbersome for my needs. It felt clunky and glitchy to me, in ways that I think the developer has tried hard to mitigate, but I imagine Apple does not make developing this sort of app easy. (I don’t think there are any public APIs for this stuff.) Bartender seems to rely on some hacks (like screen recording) to dynamically hide and show your overflowed menu bar apps. Like iOS, macOS now has a privacy feature where you are notified when apps are using the camera, microphone, screen recording, etc. On iOS there’s a little dot indicator that shows in the status bar, on macOS there’s an icon that displays in your menu bar. One specific issue I had with Bartender, was that the privacy indicator icon for screen recording appears very frequently, which was annoying.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Hidden Bar&lt;/strong&gt;. (Free. &lt;a href=&quot;https://apps.apple.com/us/app/hidden-bar/id1452453066&quot;&gt;Mac App Store&lt;/a&gt;, &lt;a href=&quot;https://github.com/dwarvesf/hidden&quot;&gt;GitHub&lt;/a&gt;). This one is extremely lightweight, providing a simple chevron “&amp;lt;” icon to expand and collapse the extra icons. You can customize which icons are always shown, and which are hidden when the menu is collapsed.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Say No to Notch&lt;/strong&gt;. (Free with IAPs, &lt;a href=&quot;https://apps.apple.com/us/app/say-no-to-notch/id1639306886&quot;&gt;Mac App Store&lt;/a&gt;). This is another heavy-handed approach. This one adds a letterboxed-style black bar to the top of your screen and shifts the entire menu bar down below the notch — along with the entire contents of your screen. I do not like the reduced screen real estate that this creates.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of these apps have pros and cons. Unfortunately, I was not quite satisfied with any of them. However, your experience and preferences might be very different!&lt;/p&gt;

&lt;div class=&quot;d-block bg-light-dark pt-3 px-3 pb-0 my-3 border border-2 border-secondary rounded update-notice&quot;&gt;
    &lt;h5 id=&quot;updated-29-may-2024&quot;&gt;
        &lt;a href=&quot;#updated-29-may-2024&quot; class=&quot;text-reset&quot;&gt;&lt;i class=&quot;bi bi-link&quot;&gt;&lt;/i&gt; Update&lt;/a&gt;
        &lt;small class=&quot;text-body-secondary&quot; title=&quot;29 May 2024 06:20:52 PM PDT&quot;&gt;
            &lt;i&gt;29 May 2024&lt;/i&gt;
        &lt;/small&gt;
    &lt;/h5&gt;
    
&lt;p&gt;Thanks to reader &lt;a href=&quot;https://github.com/thielem&quot;&gt;Moritz Thiele&lt;/a&gt; who &lt;a href=&quot;https://github.com/jessesquires/jessesquires.com/issues/194&quot;&gt;suggested some&lt;/a&gt; alternative third-party solutions. They use &lt;a href=&quot;https://github.com/zkondor/znotch&quot;&gt;zNotch&lt;/a&gt; in combination with &lt;a href=&quot;https://www.raycast.com&quot;&gt;Raycast&lt;/a&gt; shortcuts.&lt;/p&gt;

&lt;/div&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;As noted above, I have opted for the collection of first-party workarounds. For me, all of the third-party apps felt cumbersome, wonky, lacking, or simply did not match my aesthetic taste. That’s not to say they aren’t great apps and creative solutions — they just aren’t for me. In any case, I hope this post has been helpful if you’ve been searching for solutions to the MacBook notch problem.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2023/12/16/macbook-notch-and-menu-bar-fixes/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
    
    <entry xml:lang="en">
        
        
        
        

        

        
        

        <id>https://www.jessesquires.com/blog/2023/12/15/mastodon/</id>
        <link href="https://www.jessesquires.com/blog/2023/12/15/mastodon/" />
        <title>Find me on Mastodon</title>
        <published>2023-12-15T16:28:37-08:00</published>
        <updated>2023-12-15T16:28:37-08:00</updated>

        <category term="essays" />
        <category term="social-media" /><category term="mastodon" />
        <summary type="html">&lt;p&gt;With the recent announcement that &lt;a href=&quot;https://www.theverge.com/2023/12/13/24000120/threads-meta-activitypub-test-mastodon&quot;&gt;Threads.net is starting to integrate with ActivityPub&lt;/a&gt;, I figured it would be a good time to remind folks that you can follow me on Mastodon at &lt;a href=&quot;https://mastodon.social/@jsq&quot;&gt;@jsq@mastodon.social&lt;/a&gt;. Many of my connections have moved over from Twitter — but sadly, not all of them. Although, some have moved over to Threads. If you would like to join &lt;a href=&quot;https://mastodon.social&quot;&gt;Mastodon.social&lt;/a&gt;, you can &lt;a href=&quot;https://mastodon.social/invite/sHEgnWM2&quot;&gt;use my invite link&lt;/a&gt;.&lt;/p&gt;

</summary>

        <content type="html" xml:base="https://www.jessesquires.com" xml:lang="en">
            &lt;p&gt;With the recent announcement that &lt;a href=&quot;https://www.theverge.com/2023/12/13/24000120/threads-meta-activitypub-test-mastodon&quot;&gt;Threads.net is starting to integrate with ActivityPub&lt;/a&gt;, I figured it would be a good time to remind folks that you can follow me on Mastodon at &lt;a href=&quot;https://mastodon.social/@jsq&quot;&gt;@jsq@mastodon.social&lt;/a&gt;. Many of my connections have moved over from Twitter — but sadly, not all of them. Although, some have moved over to Threads. If you would like to join &lt;a href=&quot;https://mastodon.social&quot;&gt;Mastodon.social&lt;/a&gt;, you can &lt;a href=&quot;https://mastodon.social/invite/sHEgnWM2&quot;&gt;use my invite link&lt;/a&gt;.&lt;/p&gt;

&lt;!--excerpt--&gt;

&lt;p&gt;As I &lt;a href=&quot;/blog/2022/12/14/mastodon/&quot;&gt;previously wrote&lt;/a&gt;, Mastodon is pretty fucking awesome and I really enjoy it. It is currently my only (active) social media account. I am &lt;a href=&quot;/blog/2023/02/06/goodbye-twitter/&quot;&gt;no longer using Twitter&lt;/a&gt;, for reasons I hope should be obvious by now. Seriously, what a shitshow. However, my posts on this blog &lt;strong&gt;are&lt;/strong&gt; still automatically posted to Twitter for now. (That’s another reason for this post, so I don’t have to login to that awful website.)&lt;/p&gt;

&lt;p&gt;I have no plans to create an account on Threads. Let’s be honest, Facebook is truly not that much better than Twitter. All (for-profit) social media is predatory, manipulative, invasive, and socially destructive.&lt;/p&gt;

&lt;p&gt;If you were hoping to find me on Threads, sorry. But if Threads follows through on its promise to federate, then that shouldn’t be a problem for either of us.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;

&lt;p&gt;
    &lt;i&gt;Originally published on &lt;a href=&quot;https://www.jessesquires.com/blog/2023/12/15/mastodon/&quot;&gt;jessesquires.com&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/hire-me/&quot;&gt;Hire me&lt;/a&gt;&lt;/b&gt; for iOS freelance and contracting work.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.hexedbits.com&quot;&gt;Buy&lt;/a&gt;&lt;/b&gt; my apps.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://www.jessesquires.com/sponsor/&quot;&gt;Sponsor&lt;/a&gt;&lt;/b&gt; my blog and open source projects.&lt;br&gt;&lt;/i&gt;
&lt;/p&gt;

        </content>
    </entry>
    
     
</feed>
