<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Xcode on kunat.dev</title>
    <link>https://kunat.dev/tags/xcode/</link>
    <description>Recent content in Xcode on kunat.dev</description>
    <generator>Hugo -- 0.147.5</generator>
    <language>en-us</language>
    <copyright>2025 kunat.dev</copyright>
    <lastBuildDate>Wed, 11 Jun 2025 19:11:09 +0200</lastBuildDate>
    <atom:link href="https://kunat.dev/tags/xcode/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>How to Use Xcode 16&#39;s Buildable Folders with CocoaPods</title>
      <link>https://kunat.dev/notes/xcode16-folders-pods/</link>
      <pubDate>Wed, 11 Jun 2025 19:11:09 +0200</pubDate>
      <guid>https://kunat.dev/notes/xcode16-folders-pods/</guid>
      <description>&lt;p&gt;A few months ago, I decided to try Xcode&amp;rsquo;s new &lt;a href=&#34;https://dimillian.medium.com/why-you-should-use-xcode-16-buildable-folders-instead-of-groups-6f438611914d&#34; target=&#34;_blank&#34; &gt;buildable folders&lt;/a&gt; feature in my project. However, right after converting all my groups to folders, I discovered that CocoaPods had stopped working. Attempting to install project dependencies resulted in the following error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pod install
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;❌ ArgumentError - &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;Xcodeproj&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; Unable to find compatibility version string &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; object version &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;70&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At the time, I updated CocoaPods to the latest version, confirmed my project format was set to the latest Xcode version (16.3), and tried again. When the issue persisted, I assumed CocoaPods was incompatible with buildable folders and that I would need to migrate to Swift Package Manager (SPM) to use them. This conclusion seemed to be confirmed by several closed GitHub issues where the suggested workaround was to manually convert folders back to groups.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>A few months ago, I decided to try Xcode&rsquo;s new <a href="https://dimillian.medium.com/why-you-should-use-xcode-16-buildable-folders-instead-of-groups-6f438611914d" target="_blank" >buildable folders</a> feature in my project. However, right after converting all my groups to folders, I discovered that CocoaPods had stopped working. Attempting to install project dependencies resulted in the following error:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ pod install
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>❌ ArgumentError - <span style="color:#f92672">[</span>Xcodeproj<span style="color:#f92672">]</span> Unable to find compatibility version string <span style="color:#66d9ef">for</span> object version <span style="color:#e6db74">`</span>70<span style="color:#e6db74">`</span>.
</span></span></code></pre></div><p>At the time, I updated CocoaPods to the latest version, confirmed my project format was set to the latest Xcode version (16.3), and tried again. When the issue persisted, I assumed CocoaPods was incompatible with buildable folders and that I would need to migrate to Swift Package Manager (SPM) to use them. This conclusion seemed to be confirmed by several closed GitHub issues where the suggested workaround was to manually convert folders back to groups.</p>
<p>However, it turns out you don&rsquo;t have to wait to use Xcode&rsquo;s buildable folders with CocoaPods! The solution is surprisingly simple: set the <strong>Project Format</strong> in your project&rsquo;s settings to <strong>Xcode 16.0</strong>. Any other selection will trigger the <code>Xcodeproj</code> error.</p>
<p><img alt="hello there" loading="lazy" src="/notes/images/xcode16-folders-pods-header.jpg"></p>
<p>With that single change, you can start taking full advantage of Xcode&rsquo;s buildable folders in your CocoaPods projects!</p>
<h3 id="references">References</h3>
<ul>
<li><a href="https://github.com/CocoaPods/Xcodeproj/pull/985" target="_blank" >Xcodeproj PR #985</a></li>
<li><a href="https://github.com/CocoaPods/CocoaPods/issues/12671#issuecomment-2467142931" target="_blank" >CocoaPods Issue #12671</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Adding Swift Package Manager Support to a Legacy Objective-C Project</title>
      <link>https://kunat.dev/notes/spm-support-objc-project/</link>
      <pubDate>Tue, 03 Jun 2025 16:50:49 +0200</pubDate>
      <guid>https://kunat.dev/notes/spm-support-objc-project/</guid>
      <description>&lt;p&gt;I recently needed to add Swift Package Manager support to a legacy package that one of my projects was using. The package was originally distributed with CocoaPods. The primary reason for migrating from CocoaPods to SPM is that CocoaPods entered &lt;a href=&#34;https://blog.cocoapods.org/CocoaPods-Support-Plans/&#34; target=&#34;_blank&#34; &gt;maintenance mode&lt;/a&gt; a few months ago. Removing it will future-proof our projects and allow us to use the latest features, such as the &lt;a href=&#34;https://dimillian.medium.com/why-you-should-use-xcode-16-buildable-folders-instead-of-groups-6f438611914d&#34; target=&#34;_blank&#34; &gt;buildable folders&lt;/a&gt; introduced in Xcode 16.&lt;/p&gt;
&lt;h2 id=&#34;assumptions&#34;&gt;Assumptions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Since this was an internal package, I did not need to maintain CocoaPods support. The goal was to replace it entirely with SPM. It is certainly possible to keep both, but that would require some additional work.&lt;/li&gt;
&lt;li&gt;The project I was working on is considered legacy; no one has touched it in about six years. Once this task is complete, I hope no one will need to update it for another half-dozen years. This allowed me to take some shortcuts, like integrating a third-party dependency directly into the codebase instead of spending time updating it to the latest version.&lt;/li&gt;
&lt;li&gt;All internal dependencies of your framework already support SPM.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With that said, let&amp;rsquo;s get started.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I recently needed to add Swift Package Manager support to a legacy package that one of my projects was using. The package was originally distributed with CocoaPods. The primary reason for migrating from CocoaPods to SPM is that CocoaPods entered <a href="https://blog.cocoapods.org/CocoaPods-Support-Plans/" target="_blank" >maintenance mode</a> a few months ago. Removing it will future-proof our projects and allow us to use the latest features, such as the <a href="https://dimillian.medium.com/why-you-should-use-xcode-16-buildable-folders-instead-of-groups-6f438611914d" target="_blank" >buildable folders</a> introduced in Xcode 16.</p>
<h2 id="assumptions">Assumptions</h2>
<ul>
<li>Since this was an internal package, I did not need to maintain CocoaPods support. The goal was to replace it entirely with SPM. It is certainly possible to keep both, but that would require some additional work.</li>
<li>The project I was working on is considered legacy; no one has touched it in about six years. Once this task is complete, I hope no one will need to update it for another half-dozen years. This allowed me to take some shortcuts, like integrating a third-party dependency directly into the codebase instead of spending time updating it to the latest version.</li>
<li>All internal dependencies of your framework already support SPM.</li>
</ul>
<p>With that said, let&rsquo;s get started.</p>
<h2 id="how">How?</h2>
<p>I&rsquo;ll assume your framework has no internal dependencies that require the same SPM treatment. If it does, start with the bottom-most dependency and work your way up.</p>
<h3 id="external-dependencies">External Dependencies</h3>
<p>The first step is to update all external dependencies to use SPM.</p>
<p>In my case, one of the dependencies was <a href="https://github.com/mxcl/PromiseKit" target="_blank" >PromiseKit</a>, which was stuck six major versions behind the latest release. Since the project was considered legacy and no further development was planned, I decided to cut some corners.</p>
<p>Instead of spending time updating PromiseKit from version 1 through 7, I opted to add SPM support to the version my project was currently using. This approach required significantly less work and proved to be the right choice.</p>
<h3 id="removing-the-old-dependency-manager">Removing the Old Dependency Manager</h3>
<p>With all dependencies migrated to SPM, you should now be able to remove the old dependency manager from your project.</p>
<h3 id="adding-a-swift-package-manifest">Adding a Swift Package Manifest</h3>
<p>Next, initialize a new Swift package in your project&rsquo;s root directory:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>swift package init
</span></span></code></pre></div><p>This will create a <code>Package.swift</code> file. Here is a basic configuration:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span><span style="color:#75715e">// swift-tools-version: 6.1</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">PackageDescription</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">let</span> package = Package(
</span></span><span style="display:flex;"><span>    name: <span style="color:#e6db74">&#34;MyPackage&#34;</span>,
</span></span><span style="display:flex;"><span>    products: [
</span></span><span style="display:flex;"><span>        .library(
</span></span><span style="display:flex;"><span>            name: <span style="color:#e6db74">&#34;MyPackage&#34;</span>,
</span></span><span style="display:flex;"><span>            targets: [<span style="color:#e6db74">&#34;MyPackage&#34;</span>]),
</span></span><span style="display:flex;"><span>    ],
</span></span><span style="display:flex;"><span>    targets: [
</span></span><span style="display:flex;"><span>        .target(
</span></span><span style="display:flex;"><span>            name: <span style="color:#e6db74">&#34;MyPackage&#34;</span>),
</span></span><span style="display:flex;"><span>        .testTarget(
</span></span><span style="display:flex;"><span>            name: <span style="color:#e6db74">&#34;MyPackageTests&#34;</span>,
</span></span><span style="display:flex;"><span>            dependencies: [<span style="color:#e6db74">&#34;MyPackage&#34;</span>]
</span></span><span style="display:flex;"><span>        ),
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>Move your project&rsquo;s source files to <code>Sources/MyPackage</code> and try to build the project. You will likely encounter one of the following errors:</p>
<pre tabindex="0"><code>SomeHeaderFile.h:19:9 &#39;AnotherHeaderFile.h&#39; file not found
</code></pre><p>or</p>
<pre tabindex="0"><code>public headers (&#34;include&#34;) directory path for &#39;MyPackage&#39; is invalid or not contained in the target
</code></pre><p>These errors indicate that the compiler cannot locate your project&rsquo;s header files. By default, SPM looks for public headers in an <code>include</code> directory.</p>
<p>Create an <code>include</code> directory inside <code>Sources/MyPackage</code> and move all of your project&rsquo;s public headers there.</p>
<p>If this structure doesn&rsquo;t suit your project, you can define a custom path for your public headers by specifying <code>publicHeadersPath</code> in your <code>Package.swift</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span>    targets: [
</span></span><span style="display:flex;"><span>        .target(
</span></span><span style="display:flex;"><span>            name: <span style="color:#e6db74">&#34;MyPackage&#34;</span>,
</span></span><span style="display:flex;"><span>            publicHeadersPath: <span style="color:#e6db74">&#34;some/custom/path&#34;</span>
</span></span><span style="display:flex;"><span>        ),
</span></span><span style="display:flex;"><span>        .testTarget(
</span></span><span style="display:flex;"><span>            name: <span style="color:#e6db74">&#34;MyPackageTests&#34;</span>,
</span></span><span style="display:flex;"><span>            dependencies: [<span style="color:#e6db74">&#34;MyPackage&#34;</span>]
</span></span><span style="display:flex;"><span>        ),
</span></span><span style="display:flex;"><span>    ]
</span></span></code></pre></div><p><strong>How do I locate my project&rsquo;s public headers?</strong></p>
<p>Your project most likely had an Xcode project file (<code>.xcproj</code>) before you started adding SPM support. You can find a list of your public headers in the &ldquo;Build Phases&rdquo; tab, under the &ldquo;Headers&rdquo; section.</p>
<p><img alt="changedetection" loading="lazy" src="/notes/images/spm-objc-public-headers.jpg"></p>
<p>After correctly configuring the public header files, your project might build. If it still doesn&rsquo;t, you may have other header files that need to be discoverable. If that&rsquo;s the case, you&rsquo;ll need to add search paths for your private header files using <code>headerSearchPath</code> in your cSettings:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span>.target(
</span></span><span style="display:flex;"><span>    name: <span style="color:#e6db74">&#34;MyPackage&#34;</span>,
</span></span><span style="display:flex;"><span>    cSettings: [
</span></span><span style="display:flex;"><span>        .headerSearchPath(<span style="color:#e6db74">&#34;Payments&#34;</span>),
</span></span><span style="display:flex;"><span>        .headerSearchPath(<span style="color:#e6db74">&#34;Payments/ApplePayPaymentsService&#34;</span>),
</span></span><span style="display:flex;"><span>        .headerSearchPath(<span style="color:#e6db74">&#34;Transactions/Components&#34;</span>)
</span></span><span style="display:flex;"><span>    ]),
</span></span></code></pre></div><p>It&rsquo;s up to you whether you want to group all private header files in a single directory (<code>.headerSearchPath(&quot;Path/To/My/Private/Headers&quot;)</code>) or add all existing directories one by one using their current locations as I did in the snippet above.</p>
<p>With that done, you should now be able to build your project.</p>
<h2 id="troubleshooting">Troubleshooting</h2>
<p>In my case, after migrating all dependencies to SPM and adding the package manifest, I was able to delete the Xcode project file entirely. If that is not the case for you, you might encounter additional issues.</p>
<p>One common problem is the error <code>fatal error: framework '&lt;some_framework&gt;' not found</code> when trying to import an SPM dependency into an Objective-C codebase in a project managed via an Xcode project file (<code>.xcproj</code>).</p>
<p>In this situation, you may need to experiment with your project settings. <a href="https://developer.apple.com/forums/thread/120152" target="_blank" >This thread</a> on the Apple Developer Forums is an good starting point.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Once these steps are completed, you should be able to build your project. If you add your new package as a dependency via SPM, you can import it like any other Swift package. The same rules apply.</p>
<p><strong>Swift:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">MyPackage</span>
</span></span></code></pre></div><p><strong>Objective-C:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-objectivec" data-lang="objectivec"><span style="display:flex;"><span>@import MyPackage;
</span></span></code></pre></div><h2 id="resources">Resources</h2>
<p>What helped me the most was looking at examples of existing Objective-C projects with SPM support. The following repositories are worth exploring:</p>
<ul>
<li><a href="https://github.com/PSPDFKit/PDFXKit" target="_blank" >PSPDFKit/PDFXKit</a>: A drop-in replacement for Apple&rsquo;s PDFKit, powered by the PSPDFKit framework.</li>
<li><a href="https://github.com/AliSoftware/OHHTTPStubs" target="_blank" >AliSoftware/OHHTTPStubs</a>: A library to easily stub your network requests.</li>
<li><a href="https://github.com/Mantle/Mantle" target="_blank" >Mantle/Mantle</a>: Model framework for Cocoa and Cocoa Touch</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Getting Back Up to Speed with Objective-C in 2025</title>
      <link>https://kunat.dev/notes/getting-up-to-speed-with-objectivec/</link>
      <pubDate>Thu, 03 Apr 2025 10:10:52 +0200</pubDate>
      <guid>https://kunat.dev/notes/getting-up-to-speed-with-objectivec/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve mostly moved away from using Objective-C over the years. While it still appears in some of the projects I work on, it’s been quite some time since I last had to work on a feature written entirely in Objective-C. These days, I typically encounter it when integrating a new feature written in Swift into an older codebase.&lt;/p&gt;
&lt;p&gt;Recently, however, I needed to brush up on Objective-C for a job I was interviewing for.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve mostly moved away from using Objective-C over the years. While it still appears in some of the projects I work on, it’s been quite some time since I last had to work on a feature written entirely in Objective-C. These days, I typically encounter it when integrating a new feature written in Swift into an older codebase.</p>
<p>Recently, however, I needed to brush up on Objective-C for a job I was interviewing for.</p>
<p>Below is a list of strategies that helped me quickly get back up to speed with Objective-C in about 6–8 hours. The list is fairly comprehensive—it should refresh your memory and might even teach you something new. That said, if you’re completely new to the language, YMMV.</p>
<h2 id="language-fundamentals-objective-c-for-swift-developers-by-paul-hudson">Language Fundamentals: <em>Objective-C for Swift Developers</em> by Paul Hudson</h2>
<p>I can’t recommend <a href="https://www.hackingwithswift.com/store/objective-c-for-swift-developers" target="_blank" >this book</a> enough. It’s only 150 pages long and covers a wide range of language features and quirks you’ve likely forgotten—<a href="http://fuckingblocksyntax.com" target="_blank" >block syntax, anyone?</a></p>
<p>One tip: have a playground project open in Xcode while you read. If you’re not sure how to create an Objective-C project in Xcode 16, use <a href="/notes/xcode-objc-project-template/" >this guide</a>.</p>
<h2 id="leetcode-challenges">LeetCode Challenges</h2>
<p>LeetCode doesn’t support Objective-C directly in its web interface, but that’s not an issue in the age of LLMs!</p>
<p>Since my goal was to refresh my knowledge of basic language features, I focused primarily on easy challenges. By &ldquo;basic language features,&rdquo; I mean working with strings, arrays, and simple data structures.</p>
<blockquote>
<p>I definitely didn’t want to spend time solving complex problems like counting islands in a binary matrix (looking at you, “Number of Islands II,” problem #305).</p></blockquote>
<p>I selected a few easy problems and worked through them in Xcode. <a href="https://claude.ai/" target="_blank" >Claude</a> was particularly helpful in generating test cases to validate my solutions.</p>
<h2 id="working-with-large-codebases">Working with Large Codebases</h2>
<p>The final exercise was exploring a larger project written entirely in Objective-C. I started with the App Delegate and used an LLM to answer questions about anything that seemed unclear. I focused on understanding how common patterns were implemented—things like singletons, callback-based APIs, and delegate patterns.</p>
<p>Here are some projects I recommend checking out:</p>
<ul>
<li><a href="https://github.com/adium/adium" target="_blank" >Adium</a></li>
<li><a href="https://github.com/Kaakati/iOS-Objective-C-Library" target="_blank" >A curated list of awesome iOS ecosystem libraries written in Objective-C</a></li>
</ul>
<hr>
<p>That’s it! I was genuinely surprised by how much fun this process turned out to be—and also by how much I’d forgotten about Objective-C. Hopefully, this time around, the block syntax will stick with me longer!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Region-Specific Language Control: Implementing Forced Localization in iOS Apps</title>
      <link>https://kunat.dev/notes/removing-unwanted-localization-files/</link>
      <pubDate>Wed, 02 Apr 2025 22:08:53 +0200</pubDate>
      <guid>https://kunat.dev/notes/removing-unwanted-localization-files/</guid>
      <description>&lt;p&gt;My current project has a single target and multiple schemes. Each scheme represents the same app for different countries/regions. It&amp;rsquo;s essentially the same application with minor adjustments made for each of the supported jurisdictions.&lt;/p&gt;
&lt;p&gt;In Xcode projects, you define supported languages at the project level (not scheme level). Because of this structure, each app variant inherits the same language settings.&lt;/p&gt;
&lt;p&gt;My task was to force each app variant to support only the language for the region it was released in. The French app should only support French, no matter what the user&amp;rsquo;s device language is set to, and so on. This decision was made because large portions of the app are web-based and only supported a single language.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>My current project has a single target and multiple schemes. Each scheme represents the same app for different countries/regions. It&rsquo;s essentially the same application with minor adjustments made for each of the supported jurisdictions.</p>
<p>In Xcode projects, you define supported languages at the project level (not scheme level). Because of this structure, each app variant inherits the same language settings.</p>
<p>My task was to force each app variant to support only the language for the region it was released in. The French app should only support French, no matter what the user&rsquo;s device language is set to, and so on. This decision was made because large portions of the app are web-based and only supported a single language.</p>
<h2 id="solution">Solution</h2>
<p>There are multiple ways to solve this problem. What follows is the approach that worked best for my particular use case, given the project&rsquo;s current structure:</p>
<p>First, create a <a href="https://nshipster.com/xcconfig/" target="_blank" >build configuration file</a> for each of the schemes and define the <code>LOCALIZATIONS_TO_KEEP</code> variable.</p>
<pre tabindex="0"><code class="language-plist" data-lang="plist">LOCALIZATIONS_TO_KEEP = fr
</code></pre><p>Then, add a new build phase called &ldquo;Remove unused localizations.&rdquo; Remember to position it after the &ldquo;Copy Bundle Resources&rdquo; build phase:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Keeping only </span><span style="color:#e6db74">${</span>LOCALIZATIONS_TO_KEEP<span style="color:#e6db74">}</span><span style="color:#e6db74">.lproj&#34;</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Application .app path: </span><span style="color:#e6db74">${</span>CODESIGNING_FOLDER_PATH<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Only search in the main app bundle, excluding </span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Frameworks directory and other dependency folders</span>
</span></span><span style="display:flex;"><span>find <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>CODESIGNING_FOLDER_PATH<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> -type d -name <span style="color:#e6db74">&#34;*.lproj&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  -not -path <span style="color:#e6db74">&#34;*/Frameworks/*&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  -not -path <span style="color:#e6db74">&#34;*/PlugIns/*&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  -not -path <span style="color:#e6db74">&#34;*/Watch/*&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  -not -path <span style="color:#e6db74">&#34;*/SwiftPM/*&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  | grep -v <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>LOCALIZATIONS_TO_KEEP<span style="color:#e6db74">}</span><span style="color:#e6db74">.lproj&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  | xargs rm -rf
</span></span></code></pre></div><blockquote>
<p><code>CODESIGNING_FOLDER_PATH</code> is an environment variable used in Xcode build scripts that represents the path to the app bundle being built. It typically points to the directory where the compiled application and its resources are placed before code signing occurs.</p></blockquote>
<p>You&rsquo;ll want to exclude locations where third-party dependencies might be located, because you can&rsquo;t guarantee that they support the same language you want to force the app to use.</p>
<p>You can verify that the script works by navigating to ${CODESIGNING_FOLDER_PATH} in derived data and checking if the script successfully removed all other localization files.</p>
<h2 id="alternative-solutions">Alternative Solutions</h2>
<p>This issue could be solved by setting <code>CFBundleLocalizations</code> and <code>CFBundleDevelopmentRegion</code> IF my project used separate targets instead of separate schemes for different apps.</p>
<p>Alternatively, I could dynamically select Info.plist files for each of the schemes. I opted against this approach since the project already had build configurations defined for each of the schemes.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Creating a New Objective-C Project in Xcode 16</title>
      <link>https://kunat.dev/notes/xcode-objc-project-template/</link>
      <pubDate>Tue, 25 Mar 2025 17:52:35 +0100</pubDate>
      <guid>https://kunat.dev/notes/xcode-objc-project-template/</guid>
      <description>&lt;p&gt;Update: 26/03/25&lt;/p&gt;
&lt;p&gt;It looks like Objective-C is still an option in the default iOS project template. You can access it after setting the &lt;em&gt;Interface&lt;/em&gt; to &amp;ldquo;Storyboard&amp;rdquo;. I hadn&amp;rsquo;t realized these dropdown menus were interconnected.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;hello there&#34; loading=&#34;lazy&#34; src=&#34;https://kunat.dev/notes/images/xcode-objc-project-template-2.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;All credit goes to &lt;a href=&#34;https://douglashill.co&#34; target=&#34;_blank&#34; &gt;Douglas&lt;/a&gt; for spotting this!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;tl;dr use macOS → Application → Game or Command Line Tool&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;hello there&#34; loading=&#34;lazy&#34; src=&#34;https://kunat.dev/notes/images/objc-xcode-project-template.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;Recently I wanted to refresh my memory on Objective-C. My first thought was to open Xcode, create a new project with language set to Objective-C and play with it. To my surprise, none of the iOS Application project templates allow you to select any other language than Swift.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Update: 26/03/25</p>
<p>It looks like Objective-C is still an option in the default iOS project template. You can access it after setting the <em>Interface</em> to &ldquo;Storyboard&rdquo;. I hadn&rsquo;t realized these dropdown menus were interconnected.</p>
<p><img alt="hello there" loading="lazy" src="/notes/images/xcode-objc-project-template-2.jpg"></p>
<p>All credit goes to <a href="https://douglashill.co" target="_blank" >Douglas</a> for spotting this!</p>
<hr>
<p>tl;dr use macOS → Application → Game or Command Line Tool</p>
<p><img alt="hello there" loading="lazy" src="/notes/images/objc-xcode-project-template.jpg"></p>
<p>Recently I wanted to refresh my memory on Objective-C. My first thought was to open Xcode, create a new project with language set to Objective-C and play with it. To my surprise, none of the iOS Application project templates allow you to select any other language than Swift.</p>
<p>I (mistakenly) assumed that Apple must have removed Objective-C from all templates altogether. I ended up searching for some open-source Objective-C project that I could use as a starting point.</p>
<p>Later on I learned that Objective-C language options are still alive and well in some templates for macOS.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Temporarily Disable SwiftLint plugin in Swift Packages</title>
      <link>https://kunat.dev/notes/disbale-swiftlint-plugin/</link>
      <pubDate>Tue, 11 Feb 2025 21:44:20 +0100</pubDate>
      <guid>https://kunat.dev/notes/disbale-swiftlint-plugin/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve noticed that the SwiftLint plugin can add up to 30 seconds to incremental build times. In my case, it nearly doubled the average incremental build time!&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;Xcode build timeline&#34; loading=&#34;lazy&#34; src=&#34;https://kunat.dev/notes/images/build-timeline.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;Since there’s no straightforward way to disable SwiftLint across all local packages in a project, I decided on the simplest solution: automating the process of temporarily commenting out SwiftLint from the dependencies list.&lt;/p&gt;
&lt;p&gt;In my projects, to ensure the SwiftLint plugin is automatically added to all package targets, I append the following code to each Swift package manifest:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve noticed that the SwiftLint plugin can add up to 30 seconds to incremental build times. In my case, it nearly doubled the average incremental build time!</p>
<p><img alt="Xcode build timeline" loading="lazy" src="/notes/images/build-timeline.png"></p>
<p>Since there’s no straightforward way to disable SwiftLint across all local packages in a project, I decided on the simplest solution: automating the process of temporarily commenting out SwiftLint from the dependencies list.</p>
<p>In my projects, to ensure the SwiftLint plugin is automatically added to all package targets, I append the following code to each Swift package manifest:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span><span style="color:#75715e">// Inject base plugins into each target</span>
</span></span><span style="display:flex;"><span>package.targets = package.targets.map { target <span style="color:#66d9ef">in</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> plugins = target.plugins ?? []
</span></span><span style="display:flex;"><span>    plugins.append(.plugin(name: <span style="color:#e6db74">&#34;SwiftLintBuildToolPlugin&#34;</span>, package: <span style="color:#e6db74">&#34;SwiftLintPlugins&#34;</span>))
</span></span><span style="display:flex;"><span>    target.plugins = plugins
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> target
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Here’s the shell script I use to comment out SwiftLint across all packages and create a temporary commit. You can add this to your shell configuration file:</p>
<blockquote>
<p>Make sure your git working tree is clean before running this script. Otherwise, it will include your existing changes in the commit.</p></blockquote>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e"># .zshrc</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Disable SwiftLint</span>
</span></span><span style="display:flex;"><span>dsl<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    find . <span style="color:#ae81ff">\(</span> -path <span style="color:#e6db74">&#39;*/.*&#39;</span> -prune <span style="color:#ae81ff">\)</span> -o -type f -name <span style="color:#e6db74">&#34;Package.swift&#34;</span> -print | <span style="color:#66d9ef">while</span> read -r file; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>        sed -i <span style="color:#e6db74">&#39;&#39;</span> <span style="color:#e6db74">&#39;s|plugins.append(.plugin(name: &#34;SwiftLintBuildToolPlugin&#34;, package: &#34;SwiftLintPlugins&#34;))|//plugins.append(.plugin(name: &#34;SwiftLintBuildToolPlugin&#34;, package: &#34;SwiftLintPlugins&#34;))|&#39;</span> <span style="color:#e6db74">&#34;</span>$file<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>        echo <span style="color:#e6db74">&#34;Updated: </span>$file<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">done</span>
</span></span><span style="display:flex;"><span>    git add -A <span style="color:#f92672">&amp;&amp;</span> git commit -m <span style="color:#e6db74">&#34;temp: disable swiftlint&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>Create a Safari Extension to Extract Article Content</title>
      <link>https://kunat.dev/notes/safari-extension-reader-mode/</link>
      <pubDate>Wed, 18 Dec 2024 18:59:56 +0100</pubDate>
      <guid>https://kunat.dev/notes/safari-extension-reader-mode/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;safari-extension-reader-mode-preview&#34; loading=&#34;lazy&#34; src=&#34;https://kunat.dev/notes/images/safari-extension-reader-mode-preview.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;In this article, I&amp;rsquo;ll show you how to access a web page&amp;rsquo;s content, alter it using JavaScript, and display it using your Safari extension&amp;rsquo;s UI. Specifically, I&amp;rsquo;ll demonstrate how to display an article&amp;rsquo;s content in a popover.&lt;/p&gt;
&lt;p&gt;I often use Large Language Models (LLMs) to summarize articles when I&amp;rsquo;m unsure if they&amp;rsquo;re worth reading. Without the extension we&amp;rsquo;re about to build, I&amp;rsquo;d need to manually trigger Safari&amp;rsquo;s reader mode (⌘ + ⇧ + R), copy all content, and paste it into Claude or ChatGPT. This extension is a handy tool that reduces friction!&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><img alt="safari-extension-reader-mode-preview" loading="lazy" src="/notes/images/safari-extension-reader-mode-preview.png"></p>
<p>In this article, I&rsquo;ll show you how to access a web page&rsquo;s content, alter it using JavaScript, and display it using your Safari extension&rsquo;s UI. Specifically, I&rsquo;ll demonstrate how to display an article&rsquo;s content in a popover.</p>
<p>I often use Large Language Models (LLMs) to summarize articles when I&rsquo;m unsure if they&rsquo;re worth reading. Without the extension we&rsquo;re about to build, I&rsquo;d need to manually trigger Safari&rsquo;s reader mode (⌘ + ⇧ + R), copy all content, and paste it into Claude or ChatGPT. This extension is a handy tool that reduces friction!</p>
<p>Start by creating a new project using the &ldquo;Safari App Extension&rdquo; template, or add a Safari Extension target to your project. When prompted, set <code>type</code> to Safari App Extension (the default at the time of writing).</p>
<p>After the initial setup is complete, follow <a href="https://www.polpiella.dev/safari-extensions-swiftui" target="_blank" >this guide</a> to modify the template so that you can use SwiftUI to build the UI. Follow all the instructions except for one: do not delete <code>script.js</code>. We&rsquo;ll need it to capture the web page&rsquo;s content.</p>
<p>Once that&rsquo;s done, we&rsquo;ll proceed to get the current web page&rsquo;s content, modify it to extract the article content, and display it. You can check out the complete sample project <a href="https://github.com/bkunat/SafariReaderModeSample" target="_blank" >here</a>.</p>
<ol>
<li><strong>Getting Active Safari Page</strong> (<code>getActiveSafariPage</code>)</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span><span style="color:#66d9ef">let</span> page = <span style="color:#66d9ef">try</span> await getActiveSafariPage()
</span></span></code></pre></div><ul>
<li>Uses Safari&rsquo;s extension API to retrieve the current window, tab, and page</li>
<li>Returns a <code>SFSafariPage</code> object representing the active browser page</li>
</ul>
<ol start="2">
<li><strong>Requesting Content</strong> (<code>requestAndReceiveContent</code>)</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span>page.dispatchMessageToScript(
</span></span><span style="display:flex;"><span>    withName: SafariExtensionMessage.Name.getContent.rawValue,
</span></span><span style="display:flex;"><span>    userInfo: <span style="color:#66d9ef">nil</span>)
</span></span></code></pre></div><ul>
<li>Sends a &ldquo;getContent&rdquo; message to the injected content script</li>
</ul>
<ol start="3">
<li><strong>Content Script Processing</strong> (<code>script.js</code>)</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getPageContent</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Tries to extract content in this order:
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// 1. Structured article data (JSON-LD)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// 2. Article elements with specific selectors
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// 3. Fallback to body text
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">content</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">title</span><span style="color:#f92672">:</span> document.<span style="color:#a6e22e">title</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">body</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">getReaderContent</span>().<span style="color:#a6e22e">trim</span>()
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">safari</span>.<span style="color:#a6e22e">extension</span>.<span style="color:#a6e22e">dispatchMessage</span>(<span style="color:#e6db74">&#34;pageContent&#34;</span>, { <span style="color:#a6e22e">content</span> });
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><ol start="4">
<li><strong>Message Handling</strong> (<code>SafariExtensionHandler</code>)</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span><span style="color:#66d9ef">case</span> .pageContent:
</span></span><span style="display:flex;"><span>    NotificationCenter.<span style="color:#66d9ef">default</span>.post(
</span></span><span style="display:flex;"><span>        name: NSNotification.Name(SafariExtensionMessage.Notification.messageReceived.rawValue),
</span></span><span style="display:flex;"><span>        object: <span style="color:#66d9ef">nil</span>,
</span></span><span style="display:flex;"><span>        userInfo: userInfo
</span></span><span style="display:flex;"><span>    )
</span></span></code></pre></div><ul>
<li>Receives the content message from the script</li>
<li>Posts it to NotificationCenter</li>
</ul>
<ol start="5">
<li><strong>Content Reception</strong> (<code>PopoverViewModel</code>)</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-swift" data-lang="swift"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> await notification <span style="color:#66d9ef">in</span> NotificationCenter.<span style="color:#66d9ef">default</span>
</span></span><span style="display:flex;"><span>    .notifications(named: NSNotification.Name(SafariExtensionMessage.Notification.messageReceived.rawValue))
</span></span><span style="display:flex;"><span>    .compactMap({ notification <span style="color:#66d9ef">in</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Converts notification data to WebPageContent</span>
</span></span><span style="display:flex;"><span>    })
</span></span></code></pre></div><ul>
<li>Listens for the notification with the page content</li>
<li>Converts the received data into a <code>WebPageContent</code> struct</li>
<li>Returns the structured content to be displayed in the popover</li>
</ul>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="https://www.polpiella.dev/safari-extensions-swiftui" target="_blank" >How to build a Safari extension with SwiftUI</a></li>
<li><a href="https://github.com/bkunat/SafariReaderModeSample" target="_blank" >SafariReaderModeSample | GitHub</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Xcode: Missing Package Product (local package)</title>
      <link>https://kunat.dev/notes/xcode-missing-package-product/</link>
      <pubDate>Mon, 24 Jun 2024 18:48:01 +0200</pubDate>
      <guid>https://kunat.dev/notes/xcode-missing-package-product/</guid>
      <description>&lt;p&gt;When adding a local Swift package to your Xcode project, you might encounter the following error:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Missing package product `PACKAGE_NAME`
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://kunat.dev/notes/images/xcode-missing-package-product.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;The issue often occurs when the package you&amp;rsquo;re trying to add is already open in Xcode. To resolve:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Close the package in Xcode.&lt;/li&gt;
&lt;li&gt;Close your main project.&lt;/li&gt;
&lt;li&gt;Reopen your main project.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&amp;rsquo;m not sure what might be causing this issue or why reopening the project is required (a clean build won&amp;rsquo;t do).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>When adding a local Swift package to your Xcode project, you might encounter the following error:</p>
<pre tabindex="0"><code>Missing package product `PACKAGE_NAME`
</code></pre><p><img loading="lazy" src="/notes/images/xcode-missing-package-product.jpg"></p>
<p>The issue often occurs when the package you&rsquo;re trying to add is already open in Xcode. To resolve:</p>
<ol>
<li>Close the package in Xcode.</li>
<li>Close your main project.</li>
<li>Reopen your main project.</li>
</ol>
<p>I&rsquo;m not sure what might be causing this issue or why reopening the project is required (a clean build won&rsquo;t do).</p>
<p>To learn more about local Swift packages read <a href="../swift-local-packages" >Working with Local Swift Packages</a>.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
