<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://swiftjectivec.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://swiftjectivec.com/" rel="alternate" type="text/html" /><updated>2026-04-15T10:39:17-05:00</updated><id>https://swiftjectivec.com/feed.xml</id><title type="html">Swiftjective-C</title><subtitle>Swift, SwiftUI, UIKit, iOS programming, and indie development articles by Jordan Morgan.</subtitle><author><name>Jordan Morgan</name></author><entry><title type="html">Along the Way</title><link href="https://swiftjectivec.com/Along-the-Way/" rel="alternate" type="text/html" title="Along the Way" /><published>2026-04-15T00:00:00-05:00</published><updated>2026-04-15T00:00:00-05:00</updated><id>https://swiftjectivec.com/Along-the-Way</id><content type="html" xml:base="https://swiftjectivec.com/Along-the-Way/"><![CDATA[<p>Do you know <a href="https://mastodon.social/@atow">Adam Tow</a>?</p>

<p>If not, you definitely should - he has some fantastic stories to tell. Did you know he once ran into Steve Jobs at a Sushi takeout restaurant which later led to a fairly in-depth email exchange between the two of them about the Newton? Or, that he organized a <em>Newton protest outside of Apple</em>? I learned all this from this recent talk at <a href="https://deepdishswift.com">Deep Dish Swift 26’</a>, and it was a delight.</p>

<p>Or, how about our favorite neighborhood alternative App Store developer <a href="https://x.com/rileytestut">Riley</a>? He has battled Apple tooth and nail, and somehow came out on the other side. Armed with the wit and determination only found in a man who has the patience to actually <em>make</em> another App Store, his recount of how it was all finally approved is simply hilarious. In short, he basically gave Apple an ultimatum on which of his two apps they should accept during a long notarization process. I won’t spoil it, you should just watch the talk. Especially if you’ve ever been burned by App Review, you’ll find a little poetic justice somewhere in there.</p>

<p>…and I, of course, could go on. So many great talks, and even better people.</p>

<p>This week, I began to realize I’ve…kinda been around the iOS scene awhile now? Ya know? Like, I’m not new here anymore. If you will allow it, here’s how my oldest looked when I first started on Spend Stack, and what he looks like now:</p>

<div class="image-comparison-wrapper rounded-xl not-prose">
  <div class="relative w-full overflow-hidden image-comparison-slider" id="imageComparisonSlider">
    <img src="../assets/images/ut_3.jpeg" alt="Image 1" class="w-full h-auto rounded-xl" />
    <div class="absolute top-0 left-0 w-full h-full overflow-hidden" id="imageWrapper">
      <img src="../assets/images/ut_2.jpeg" alt="Image 2" class="absolute top-0 left-0 w-full h-full object-cover rounded-xl" />
    </div>
    <div class="slider-handle" id="sliderHandle">
      <div class="slider-button">
        <div class="flex">
            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-4 w-4">
                <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
              </svg>
              <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-4 w-4">
                <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
              </svg>             
        </div>
      </div>
    </div>
    
      <div class="caption-container rounded-xl">
        <div class="caption caption-left sjc-prose" id="caption1">Tiny Benny</div>
        <div class="caption caption-right sjc-prose" id="caption2">Not-Tiny Benny</div>
      </div>
    
  </div>
</div>

<style>
  .image-comparison-wrapper {
    position: relative;
    user-select: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
  }

  .image-comparison-slider {
    position: relative;
    z-index: 0;
  }

  .slider-handle {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 4px;
    background: white;
    cursor: ew-resize;
    z-index: 10;
  }

  .slider-button {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 40px;
    height: 40px;
    background: white;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .slider-icon {
    width: 24px;
    height: 24px;
    color: #333; /* Adjust color as needed */
  }

  .caption-container {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    display: flex;
    justify-content: space-between;
    padding: 10px;
    background: rgba(255, 255, 255, 0.7);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
  }

  .caption {
    padding: 10px 15px;
    color: black;
    font-size: 14px;
    max-width: 45%;
    overflow: hidden;
    transition: opacity 0.3s ease;
  }

  .caption-left {
    text-align: left;
  }

  .caption-right {
    text-align: right;
  }
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
  const slider = document.getElementById('imageComparisonSlider');
  const sliderHandle = document.getElementById('sliderHandle');
  const imageWrapper = document.getElementById('imageWrapper');
  const caption1 = document.getElementById('caption1');
  const caption2 = document.getElementById('caption2');
  let isDragging = false;
  let currentX;

  function updateSliderPosition() {
    if (!isDragging && currentX === undefined) return;

    const sliderRect = slider.getBoundingClientRect();
    let position = (currentX - sliderRect.left) / sliderRect.width;
    position = Math.max(0, Math.min(position, 1));

    sliderHandle.style.left = `${position * 100}%`;
    imageWrapper.style.clipPath = `inset(0 ${100 - position * 100}% 0 0)`;

    // Update caption opacity
    if (caption1 && caption2) {
      const minOpacity = 0.20;
      const maxOpacity = 1;
      const opacityRange = maxOpacity - minOpacity;

      let opacity1 = minOpacity + opacityRange * (1 - position);
      let opacity2 = minOpacity + opacityRange * position;

      caption1.style.opacity = opacity1;
      caption2.style.opacity = opacity2;
    }

    requestAnimationFrame(updateSliderPosition);
  }

  function startDragging(e) {
    isDragging = true;
    currentX = e.clientX || e.touches[0].clientX;
    requestAnimationFrame(updateSliderPosition);
  }

  function stopDragging() {
    isDragging = false;
    currentX = undefined;
  }

  function onMove(e) {
    if (!isDragging) return;
    currentX = e.clientX || e.touches[0].clientX;
  }

  sliderHandle.addEventListener('mousedown', startDragging);
  sliderHandle.addEventListener('touchstart', startDragging);

  document.addEventListener('mousemove', onMove);
  document.addEventListener('touchmove', onMove, { passive: false });

  document.addEventListener('mouseup', stopDragging);
  document.addEventListener('touchend', stopDragging);

  slider.addEventListener('click', function(e) {
    currentX = e.clientX;
    requestAnimationFrame(updateSliderPosition);
  });

  // Initialize slider position
  currentX = slider.getBoundingClientRect().left + slider.offsetWidth / 2;
  requestAnimationFrame(updateSliderPosition);
});
</script>

<p><em>🥹 They grow up so fast!</em></p>

<p>And, as the years tick on, as they do for all of us, life thankfully starts to arm you with more wisdom, perspectives on life, and new angles to look at things. In an industry where, currently, everything is changing, we tend to forget the best part of any app — it’s the person behind it.</p>

<p>This is the primary reason I lament Apple’s modern approach to W.W.D.C. — the community is <em>fractured</em> at the one single time during the year when it should be united most. Going to Apple Park, I’m sure, is a fantastic experience for the few who win the lottery. Though, it’s nothing compared to when seemingly every single person in this industry was in one central place.</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/ut_1.jpeg" role="presentation" srcset="../assets/images/ut_1.jpeg" alt="WWDC 2019." /></p>

<p>AltConf going on next door, running down the halls of San Jose convention center, packed with iOS developers, designers, and Apple employees — you just can’t beat it. Every year, I plead with Apple on the developer survey to go back to this. I don’t think they ever will, though. And that’s a shame, because the relationships you formed, the people you met, the kind folks at Apple you could network with, all of those things could change the trajectory of your career. The best parts I’m talking about.</p>

<p>And, it’s the same still with social media. Twitter, in the before times, was a community and it felt like we were all there. Now, that’s obviously fractured too. Some are on Mastodon, others on Threads, a few kicking around on BlueSky, more still on X, etc. It’s changed quite a bit. For me? It was surreal to realize how others associate me with certain chapters of my career. Some personally know me from Spend Stack. Others, it’s the book series. Yet again, there’s more who associate me with Alyx or Elite Hoops. And, yeah, some hotshot bros know me as a <a href="https://x.com/farrux_hewson/status/2042201541923467373?s=20">vibe coder noob apparently</a>. And honestly? I enjoy meeting every single one of them.</p>

<p>Where was I going with this again?</p>

<p>Oh yes — the people! The relationships. Events, social media, the places we can go, the way dub dub is held — all of that stuff will always change. No matter the landscape, though, I would encourage you to get around your people at least once a year if you can. Your other indie friends, the ones who work at giant companies, new kids on the scene eager to learn and show off their app. We don’t wake up and decide to start in this career <em>just</em> to met new people, it’s not <em>why</em> we do this, but it is certainly the best part.</p>

<p>Until next time ✌️</p>]]></content><author><name>Jordan Morgan</name></author><category term="The Indie Dev Diaries" /><summary type="html"><![CDATA[What's better than the friends you made along the way? Not much!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://swiftjectivec.com/assets/images/logo.png" /><media:content medium="image" url="https://swiftjectivec.com/assets/images/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Kids and Vibe Coding: The Joy of Building</title><link href="https://swiftjectivec.com/Kids-and-Vibe-Coding-iOS-Apps/" rel="alternate" type="text/html" title="Kids and Vibe Coding: The Joy of Building" /><published>2026-04-08T00:00:00-05:00</published><updated>2026-04-08T00:00:00-05:00</updated><id>https://swiftjectivec.com/Kids-and-Vibe-Coding-iOS-Apps</id><content type="html" xml:base="https://swiftjectivec.com/Kids-and-Vibe-Coding-iOS-Apps/"><![CDATA[<p>Ah, the joy of building. Lately, my kids have acquired <em>the bug</em>. Now that they are a bit older, they’ve started to somewhat realize what I’m doing all day long, hunched over a brightly lit laptop — pecking away on a keyboard.</p>

<p>Click clack, clack clack, click click….<strong>boom</strong>, I made something. An app appears, and now they are at the age where they can comprehend that. And, well, they think that is amazing.</p>

<p>And thus, I’ve started to dip into the realm of kids and vibe coding. At first, I had an outline with something to say on this topic. But, you know what, I just threw it out. Both of my oldest kiddos, Benny (12) and Remy (9), have asked me to help them “make an app”, and we’ve had a blast.</p>

<p>What follows are just random observations in no particular order.</p>

<h3 id="they-have-no-idea-what-theyre-doing">They have no idea what they’re doing</h3>

<p>What even <em>is</em> an app to a kid? To them, anything entirely web-based for the whole experience is not an app (I raised them right!), and they were even surprised you could charge money on the web!</p>

<p>But, we all start somewhere. And, to that end, they’ve both taken completely different approaches to deciding where they should start. Remy, ever the analytical thinker among my kids, opted to spec it out. Benny? Two sheets to the wind, he just opened up Codex and let it rip. Which leads me to my first observation…</p>

<h3 id="the-prompts-are-just-brutal">The prompts are just brutal</h3>

<p>…and it’s somewhat endearing at the same time. Seeing Remy hunched over the keyboard, pecking away on one hand:</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/remy_tcg1.jpg" role="presentation" srcset="../assets/images/remy_tcg1.jpg" alt="Remy typing on a keyboard on a kitchen counter." /></p>

<p>…just tossing Codex the most open-ended instructions imaginable:</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/remy_tcg2.jpg" role="presentation" srcset="../assets/images/remy_tcg2.jpg" alt="An open-ended prompt for an LLM to make a Pokemon app." /></p>

<p>So many questions! What kind of app? Which platform? FIVE BUCKS? We using StoreKit for this, what about paywalls? The seasoned engineer in me reeled, but I wanted them to hit as many bumps and learn how to work around them as they could. Even then, though, they do get “Dad Assists” here and there. And, Remy used his to kickstart his project:</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/remy_tcg3.jpg" role="presentation" srcset="../assets/images/remy_tcg3.jpg" alt="A more in-depth follow up to the previous prompt." /></p>

<p>In this case, I mentioned he might want to say what kind of app it is, punt the money stuff for now, and hint to the LLM which general direction we’re taking things (iOS, minimum target is likely iOS 26).</p>

<p>Writing prompt cleanups have been fantastic learning and bonding moments. We get to briefly talk about why I wrote something the way I did, I try to gently explain how an LLM works, what they’re good at, what they’re not as good at, etc.</p>

<p>To that end, their prompts have improved. But even then…</p>

<h3 id="ohhhh-my-the-ux">Ohhhh my, the UX</h3>

<p>…these apps are <em>brutal</em> right now. Remy’s has, like, three search functions - all doing different things. I mean, check this number out:</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/remy_tcg4.jpg" role="presentation" srcset="../assets/images/remy_tcg4.jpg" alt="Remy's Pokemon app." /></p>

<p>It’s easy for <em>us</em> to spot the issues. However, the bit that’s surprised me, though, is that they are…aware of it? Even as a 9 year old. Is this a potential byproduct of growing up in the digital age? They know it doesn’t feel right, and they can’t really say why yet, they just know it feels off.</p>

<p>That’s hardly surprising, as design and user experience are things that take years and years to develop.</p>

<h3 id="their-imagination-is-on-steroids">Their imagination is on steroids</h3>

<p>The fact that they now live in a world where they can type in a box and get something working has, quite literally, ignited their creativity, curiosity, and excitement. The boys are huge fans of <a href="https://neal.fun">neal.fun</a>, especially his fantastic auction game:</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/remy_tcg5.jpg" role="presentation" srcset="../assets/images/remy_tcg5.jpg" alt="neal.fun's auction game." /></p>

<p>Remy, unshackled with unimaginable power at his disposal, immediately u-turned on a bug fix and just threw that at Codex and instructed it to add a Pokemon card version in his app. And, it came pretty close one-shotting it:</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/remy_tcg7.jpg" role="presentation" srcset="../assets/images/remy_tcg7.jpg" alt="Remy's version of an auction game" /></p>

<p>This is the good stuff. Being able to think of something fun and have it come to life. It’s the same dopamine hit you get from building with legos, finishing a picture, or generally constructing a thought and turning it into something tangible. While it’s silly in this context, these are the moments that will inspire them to continue.</p>

<h3 id="the-skills-still-pay">The skills still pay</h3>

<p>The one ephiphany I’ve had with all of this (aside from how insane it is that my kids can get a functioning app working) is that the skills we have are magnified now, perhaps more than ever. We’ve all been so uneasy about the implication of “anyone can make an app”, we haven’t stopped much to consider that our hard-earned skills are still the thing that makes a good app.</p>

<p>You can throw all the skill files you want at an LLM (And you should! They’re great!), but they still have critical limitations. Take text, for example. What would you instinctively do if you have a view whose text was truncated, clipping, or otherwise not fitting into a container?</p>

<p>The layman would say, “Can you make this text not clip?” The hardened iOS veteran might say, “Apply a .minimumScaleFactor modifier to this text, and ensure you’re using a Text style and not hard coding the font size.”</p>

<p>Off the cuff? Visually, you may get the same result at first. But, well — if you know, you know. One is objectively better. And those of us who can speak the language will still be making the best apps. The same skills still win, <a href="https://www.swiftjectivec.com/The-Abstraction-Layer/">as I mentioned before</a>, the tools have simply changed. It does, however, highlight my one lingering worry: That the next generation my take an ignorance through obstinance stance towards coding. If the ease of it all could tempt a kid (or anyone) to never bother learning how something truly works, they’d be missing out on some required skills.</p>

<h3 id="the-sweet-spot">The sweet spot</h3>

<p>While the kids have been using Codex, I think I’d opt to stick them in <a href="https://bitrig.com">Bitrig’s nascent mac app</a>. It’s a bit more harnessed, in a good way, and I think the results would be a little better. The app Anything spun something up quick, in Expo. With Codex, I have a ton of credits, so we’ve stuck with that. Personally? I prefer Codex for coding anyways, especially since they put a frontend on top of the CLI. If it becomes a native mac app, I’ll probably never close it.</p>

<p>But for them? I can’t stop thinking about how a Macbook Neo with Bitrig would be a fun addition to the Morgan family. You kinda sorta need to make sure these kids don’t blow up your house with this stuff, which, well, depending on your permissions — could certainly happen.</p>

<h3 id="wrapping-up">Wrapping up</h3>

<p>Reflecting on the vibe coding experiences with the kids, I only have one prevailing thought: we are all having so much <em>fun</em>. The kids are becoming increasingly interested in what their dad actually does for a career now. Remy, especially, is getting into it. He’s developed the ineluctable urge to <em>make something</em>. The last two days he has come home from school, it’s “Dad, can I work on my app!?” He even stuck some post-it notes to his closet so he wouldn’t forget what to work on the next day:</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/remy_tcg6.jpg" role="presentation" srcset="../assets/images/remy_tcg6.jpg" alt="Month of marketing gameplan." /></p>

<p>Until next time ✌️</p>]]></content><author><name>Jordan Morgan</name></author><category term="A.I." /><summary type="html"><![CDATA[My kids have discovered vibe coding. What follows is a journal over our adventures so far.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://swiftjectivec.com/assets/images/logo.png" /><media:content medium="image" url="https://swiftjectivec.com/assets/images/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The Abstraction Layer</title><link href="https://swiftjectivec.com/The-Abstraction-Layer/" rel="alternate" type="text/html" title="The Abstraction Layer" /><published>2026-03-20T00:00:00-05:00</published><updated>2026-03-20T00:00:00-05:00</updated><id>https://swiftjectivec.com/The-Abstraction-Layer</id><content type="html" xml:base="https://swiftjectivec.com/The-Abstraction-Layer/"><![CDATA[<p>When I first began my degree in the field of CompSci, I picked up a book: <a href="https://www.amazon.com/Code-Language-Computer-Hardware-Software/dp/0137909101">“Code”, by Charles Petzold</a>. I don’t distinctly remember if it necessarily changed my life or sparked my love of software development. But, I remember with certainty that it <em>did</em> give me the gift of curiosity.</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/code_cp.png" role="presentation" srcset="../assets/images/code_cp.png" alt="Code by Charles Petzold." /></p>

<p>What was actually happening when I was typing in Visual Basic on some Windows desktop in a computer lab at Ozarks Technical Community College? How did the characters transform and…just…<em>do stuff</em>? I wanted to know, and that book went in on it all. I remember learning about how every computer begins with simple, basic physical states: on and off. Open, and closed. And, of course, 1 or 0.</p>

<p>From there, those binary states can be represented by all sorts of interesting modes: switches, relays, and then transistors. Building on that some more, wire enough of them together and now we get to the interesting stuff. Logic gates, adders, memory, clocks, and eventually down the line — a CPU that can read instructions from memory! And, execute them!</p>

<p>But in my day to day? That’s all abstracted away from me, as it should be. It’s just as well, though, since abstractions have long been a core part of our indsutry. In fact, who remembers interviews in the aughts when object orientated programming was king? We had to remember our favorite acronym, A.P.I.E. — <em>abstraction</em>, polymorphism, inheritance, and encapsulation. So, in that sense, code has long been an abstraction layer. It’s one that sits comfortably above the raw hardware, <em>but</em> it’s close enough that, after enough of said translation, it can still make silicon do something real and tangible in the world.</p>

<p>I’ve been thinking a lot about abstraction layers lately.</p>

<p>What the “abstraction” layer is now, and how we define it, is changing. And, naturally, it has me wondering. With the rise of agentic engineering, I’ve started to consider something I thought impossible only a few years ago:</p>

<p><span class="relative inline-block not-prose pb-2 md:pb-[0.25px]"><span class="bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-purple-500 font-semibold">Is code going to be abstracted away, entirely, soon?</span><span class="absolute left-0 bottom-0 w-full h-1.5" style="background-image: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 6' preserveAspectRatio='none'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='0%25'%3E%3Cstop offset='0%25' stop-color='%233b82f6'/%3E%3Cstop offset='100%25' stop-color='%238b5cf6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M0,6 Q50,0 100,6' fill='none' stroke='url(%23g)' stroke-width='1.75'/%3E%3C/svg%3E&quot;); background-size: 100% 100%; background-repeat: no-repeat;"></span></span></p>

<p>And, yeah, I realize the word “soon” is doing some <em>heavy lifting</em> here.</p>

<p>Once the frontier models improve, <em>how soon until it matters if you read what it outputs?</em> No, seriously, I’m wondering that lately. I know, I know — <strong>I KNOW</strong> — it’s not there yet. Today. But, I think eventually, it will be. And what does that mean for software engineering? Is it then prompt engineering? Will knowing how the all the pieces fit together matter even more, or less?</p>

<p>How much longer is understanding code a competitive advantage? I know it certainly is today. And, lest I’m not making my stance clear enough, you <em>do need</em> to understand code to succeed in my opinion. In 2026, most of us in this field grew up figuring out its intimate inner workings. That’s why those with years of experience are currently building software at a blistering pace. Though, how long will it keep us ahead of everyone else? Because when it becomes abstracted away, well — that’s a different world we’ll live in.</p>

<section class="bg-indigo-50 border-l-8 border-indigo-500 text-gray-800 rounded-md my-10 dark:text-gray-300 dark:bg-gray-600 dark:border-indigo-700 ">
    <div class="px-5 font-mono">
        <p class="pt-2 md:pt-4"><span class="text-purple-600 dark:text-purple-400">let</span> concatenatedThoughts = <span class="text-red-600">"""</span></p>
        <p class="text-gray-600 dark:text-gray-300 text-sm md:text-base">Of course, we will always need people in the world who understand code. In the same way that we need people in the world who have a professional, expert knowledge of how computers actually work. We can't afford to lose that, obviously, because when one machine drives another — both machines will always need a captain.</p>
        <p class="pb-2 md:pb-4 text-gray-600 text-base"><span class="text-red-600">"""</span></p>
    </div>
</section>

<p>I’m pondering here, not panicking. We need to remember that abstraction layers are not inherently bad. In fact, by most measures, not only are they incredibly useful — they are necessary. I don’t want to manually tweak logic gates. I want to write code to do it. Years ago, there was undoubtedly a time where <em>writing</em> code woud seem as outlandish as using a chat interface to produce that same code for you.</p>

<p>Naturally, as the future progresses and technology advances, making software will look different, too. The future of software engineering versus “AI does it all” hopefully will not be a zero-sum game. More abstraction does not <em>automatically</em> mean we need less understanding, but I think it does most likely change “what” is important to understand.</p>

<p>I don’t know the answer, and to be honest — I’m not scared of the answer, either. I’ve made my way in this industry by being curious. People like to say it’s all changing so fast, and there’s an inordinate amount of AI tooling, advances, or models to learn. I say it’s always been that way. I’ve done well in this career because of adapting to changes. By wondering how logic gates, bits and bytes, and code eventually create a piece of software I made. And whatever the tools and technology become to keep creating those things well and with precision, I’ll keep learning.</p>

<p>When I went back to speak at my community college years ago, I told the prospective students that the only constant in our industry is change. Now, I suppose, never has that been more apt.</p>

<p>Until next time ✌️</p>]]></content><author><name>Jordan Morgan</name></author><category term="A.I." /><summary type="html"><![CDATA[Are we entering a world where code is another abstraction layer? And, how closely will the next wave of engineers pay attention to it?]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://swiftjectivec.com/assets/images/logo.png" /><media:content medium="image" url="https://swiftjectivec.com/assets/images/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Agentic Coding in Xcode 26.3 with Claude Code and Codex</title><link href="https://swiftjectivec.com/Agentic-Coding-Codex-Claude-Code-in-Xcode/" rel="alternate" type="text/html" title="Agentic Coding in Xcode 26.3 with Claude Code and Codex" /><published>2026-02-04T00:00:00-06:00</published><updated>2026-02-04T00:00:00-06:00</updated><id>https://swiftjectivec.com/Agentic-Coding-Codex-Claude-Code-in-Xcode</id><content type="html" xml:base="https://swiftjectivec.com/Agentic-Coding-Codex-Claude-Code-in-Xcode/"><![CDATA[<p>I was filled with whimsy watching OpenAI announce their Codex app<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> not even <a href="https://x.com/JordanMorgan10/status/2018401840497410297">two days ago</a>. But life comes at you fast. The very next day, <a href="https://x.com/JordanMorgan10/status/2018764327051035035">Xcode 26.3 dropped with support for agentic coding</a>.</p>

<p>As someone who is truly enjoying using agents to create software, I was ready to dive in. I’ve come to love using agents in Terminal, but I’m always up to try the shiny new thing. And if there’s something Apple does particularly well, it’s typically integrating new shiny things with a refined taste to them.</p>

<p>Here, I’ll share first impressions, and some important answers to questions I had. Also, I’ll be primarily chatting through the Codex lens.</p>

<h3 id="the-critical-infrastructure">The Critical Infrastructure</h3>

<p>Ask three different people how they use agents, and somehow you’ll get sixteen different answers. Me? I rely on the “core pillars”:</p>

<ol>
  <li><strong>agents.md</strong>: Projects I use all have a lightweight <code class="language-plaintext highlighter-rouge">agents.md</code> or <code class="language-plaintext highlighter-rouge">claude.md</code> file. Critical to how I work.</li>
  <li><strong>skills.md</strong>: The more nascent skills movement just keeps growing. Vercel, and its successful <a href="https://skills.sh">skills.sh</a> initiative, has only strengthened it. I’ve come to use several skills, and not having those would be a quick stop for me.</li>
  <li><strong>MCPs</strong>: The model context protocol is amazing to interface with services. When they were first announced, I thought of them as nothing more than an API. And, in some ways, that is true. But they aren’t APIs for you or me, they are for agents. Supabase’s MCP has saved me tons of time, “Query all of the basketball drills for this User ID”, “Do I have cascade deletes in place for X or Y”, the list goes on. They are a requirement for my agent use.</li>
</ol>

<p>If Xcode’s integration couldn’t leverage <em>any</em> one of these, I would personally have no reason to use it. I nearly moved on altogether since I realized the star of the show, Apple’s own Xcode MCP implementation, is available for other agents to use:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># CC:</span>
claude mcp add <span class="nt">--transport</span> stdio xcode <span class="nt">--</span> xcrun mcpbridge

<span class="c"># Codex:</span>
codex mcp add xcode <span class="nt">--</span> xcrun mcpbridge
</code></pre></div></div>

<p>Still, the allure of keeping things “in house” is strong. And, Apple is only going to improve their offering. As long as my core pillars were usable, I’d try it out. At first, I thought that wasn’t the case (my skills weren’t showing, for example) but I’m happy to report that it can, and does, use your existing “core pillars” with a little pageantry from your end.</p>

<h3 id="new-codex">New Codex</h3>

<p>Here’s what made it all click for me. <strong>If you already have Codex installed, think of Xcode’s Codex usage as an entirely fresh install.</strong> That makes complete sense when you zoom out and think about it (this Codex is literally for Apple development and nothing else). You can confirm this for yourself by viewing the <code class="language-plaintext highlighter-rouge">config.toml</code> for each installation:</p>

<div class="image-comparison-wrapper rounded-xl not-prose">
  <div class="relative w-full overflow-hidden image-comparison-slider" id="imageComparisonSlider">
    <img src="../assets/images/agentic_apple.jpg" alt="Image 1" class="w-full h-auto rounded-xl" />
    <div class="absolute top-0 left-0 w-full h-full overflow-hidden" id="imageWrapper">
      <img src="../assets/images/agentic_mine.jpg" alt="Image 2" class="absolute top-0 left-0 w-full h-full object-cover rounded-xl" />
    </div>
    <div class="slider-handle" id="sliderHandle">
      <div class="slider-button">
        <div class="flex">
            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-4 w-4">
                <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
              </svg>
              <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-4 w-4">
                <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
              </svg>             
        </div>
      </div>
    </div>
    
      <div class="caption-container rounded-xl">
        <div class="caption caption-left sjc-prose" id="caption1">My own config</div>
        <div class="caption caption-right sjc-prose" id="caption2">Apple's config</div>
      </div>
    
  </div>
</div>

<style>
  .image-comparison-wrapper {
    position: relative;
    user-select: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
  }

  .image-comparison-slider {
    position: relative;
    z-index: 0;
  }

  .slider-handle {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 4px;
    background: white;
    cursor: ew-resize;
    z-index: 10;
  }

  .slider-button {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 40px;
    height: 40px;
    background: white;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .slider-icon {
    width: 24px;
    height: 24px;
    color: #333; /* Adjust color as needed */
  }

  .caption-container {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    display: flex;
    justify-content: space-between;
    padding: 10px;
    background: rgba(255, 255, 255, 0.7);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
  }

  .caption {
    padding: 10px 15px;
    color: black;
    font-size: 14px;
    max-width: 45%;
    overflow: hidden;
    transition: opacity 0.3s ease;
  }

  .caption-left {
    text-align: left;
  }

  .caption-right {
    text-align: right;
  }
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
  const slider = document.getElementById('imageComparisonSlider');
  const sliderHandle = document.getElementById('sliderHandle');
  const imageWrapper = document.getElementById('imageWrapper');
  const caption1 = document.getElementById('caption1');
  const caption2 = document.getElementById('caption2');
  let isDragging = false;
  let currentX;

  function updateSliderPosition() {
    if (!isDragging && currentX === undefined) return;

    const sliderRect = slider.getBoundingClientRect();
    let position = (currentX - sliderRect.left) / sliderRect.width;
    position = Math.max(0, Math.min(position, 1));

    sliderHandle.style.left = `${position * 100}%`;
    imageWrapper.style.clipPath = `inset(0 ${100 - position * 100}% 0 0)`;

    // Update caption opacity
    if (caption1 && caption2) {
      const minOpacity = 0.20;
      const maxOpacity = 1;
      const opacityRange = maxOpacity - minOpacity;

      let opacity1 = minOpacity + opacityRange * (1 - position);
      let opacity2 = minOpacity + opacityRange * position;

      caption1.style.opacity = opacity1;
      caption2.style.opacity = opacity2;
    }

    requestAnimationFrame(updateSliderPosition);
  }

  function startDragging(e) {
    isDragging = true;
    currentX = e.clientX || e.touches[0].clientX;
    requestAnimationFrame(updateSliderPosition);
  }

  function stopDragging() {
    isDragging = false;
    currentX = undefined;
  }

  function onMove(e) {
    if (!isDragging) return;
    currentX = e.clientX || e.touches[0].clientX;
  }

  sliderHandle.addEventListener('mousedown', startDragging);
  sliderHandle.addEventListener('touchstart', startDragging);

  document.addEventListener('mousemove', onMove);
  document.addEventListener('touchmove', onMove, { passive: false });

  document.addEventListener('mouseup', stopDragging);
  document.addEventListener('touchend', stopDragging);

  slider.addEventListener('click', function(e) {
    currentX = e.clientX;
    requestAnimationFrame(updateSliderPosition);
  });

  // Initialize slider position
  currentX = slider.getBoundingClientRect().left + slider.offsetWidth / 2;
  requestAnimationFrame(updateSliderPosition);
});
</script>

<p>Apple has honed in their <code class="language-plaintext highlighter-rouge">config.toml</code> to supercharge iOS development. Notes on Liquid Glass, call outs for Foundation Models — the list goes on. Though Apple blasts the doors off of their “big” stuff at W.W.D.C., you’d be crazy to think they aren’t paying attention. How we develop software is changing, and internally, it’s clear they are humming along with it. The fact that Xcode 26.3 exists, <em>right now</em>, is proof. They didn’t just cut a new branch once Codex’s macOS app shipped.</p>

<p>A few thoughts on the core pillars:</p>

<div class="not-prose mt-8 -mb-4">
  <h3 class="bg-gradient-to-l from-blue-500 to-fuchsia-500 bg-clip-text text-transparent text-2xl font-bold leading-relaxed">
    Skill files
  </h3>
</div>
<p>In Apple’s <a href="https://developer.apple.com/documentation/xcode/setting-up-coding-intelligence">documentation</a>, they hint that each agentic option is customizable. But to what end, it’s not entirely clear:</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/agentic_docs.jpeg" role="presentation" srcset="../assets/images/agentic_docs.jpeg" alt="Month of marketing gameplan." /></p>

<p>However, opening that up reveals a lot:</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/agentic_fp.jpeg" role="presentation" srcset="../assets/images/agentic_fp.jpeg" alt="Month of marketing gameplan." /></p>

<p>A skills folder! I simply copied what I had in my existing <code class="language-plaintext highlighter-rouge">~/.codex/skills</code> over there, and I could confirm that Xcode’s Codex could now see them:</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/agentic_skills.jpeg" role="presentation" srcset="../assets/images/agentic_skills.jpeg" alt="Month of marketing gameplan." /></p>

<div class="not-prose mt-8 -mb-4">
  <h3 class="bg-gradient-to-l from-blue-500 to-fuchsia-500 bg-clip-text text-transparent text-2xl font-bold leading-relaxed">
    MCPs
  </h3>
</div>
<p>Since we know where the <code class="language-plaintext highlighter-rouge">config.toml</code> is at, moving over MCPs should not be a problem, either. Just add them in Apple’s <code class="language-plaintext highlighter-rouge">config.toml</code> and you’ll be good to go. What I’m not entirely sure about is how to kick off authentication for some MCPs that may require it. For example, to get Supabase working in Codex, I’d run <code class="language-plaintext highlighter-rouge">codex mcp login supabase</code> — but it needs to be ran out of session. But, there is no “out of session” in this case. It may just work, but I’ve not tried it yet.</p>

<div class="not-prose mt-8 -mb-4">
  <h3 class="bg-gradient-to-l from-blue-500 to-fuchsia-500 bg-clip-text text-transparent text-2xl font-bold leading-relaxed">
    agents.md
  </h3>
</div>
<p>There’s nothing to say here, it just works.</p>

<h3 id="odds-and-ends">Odds and ends</h3>

<p>So, how is the actual experience? Well, pretty nice! This is such a tiny thing, but in Terminal — removing a chunk of text <em>sucks</em>. I’m sure there is some keyboard shortcut I’m missing, or some other app I could use like iTerm or what have you, but not being able to use <code class="language-plaintext highlighter-rouge">Command+A</code> and then delete a text selection hurts. In Xcode, that’s easily done because the input is no longer running through Terminal, it’s just an AppKit text entry control.</p>

<p>Oh, and it’s pretty! Apple has leaned into a bit of a bolder text for prompt creation, and the colorful “ribbon”, synonymous with Apple Intelligence, is always a joy to look at. It’s also so beautifully native. Codex’s app, while packed full of goodies, just feels…a little <em>ugh</em>, ya know? This surprised me, since their flagship ChatGPT is native and feels incredible to use (complete with Liquid Glass on Tahoe).</p>

<p>But there are no free lunches in life. Another Codex installation, another agent to use, another spot you have to maintain skills and MCPs. Surely, the developers behind these agentic tool chains will adopt an open standard so we don’t have to worry about this. In fact, this has already been proposed by OpenAI developers for skills at least (I lost the tweet, but it’s out there).</p>

<p>We don’t know which model will “win”, or if one <em>ever</em> will. Personally, I find the competition necessary and good for their target market, developers. I personally hope none of them win and keep pushing each other to be better. But, open standards to control all of the core pillars would be welcome. When I update one skill, I now update it in three different places.</p>

<h3 id="wrapping-up">Wrapping up</h3>

<p>This is a fantastic start for Xcode. If you’re late to the Claude Code or Codex scene, this is a wonderful place to start. There’s simply no going back once you learn how to use these tools. Ideas that you wanted to hack on become doable, those dusty side project folders come alive a bit more, and you get ideas out of your head much faster. These are all good things.</p>

<p>Plus, it makes you wonder what Apple will have for us at this year’s W.W.D.C. — this release is the kind of thing you typically see there. Maybe they felt they had to respond earlier? Maybe it was just ready to go? Maybe they have #EvenMoreCoolThings coming? I dunno, but I’m eager to see.</p>

<p>It’s all moving quickly. All the way back in 2020, <a href="https://nshipster.com/as-we-may-code/">Mattt sort of saw some of this coming</a>. It’s fun to read that post back, and see how so much of it is happening today. What a time to build!</p>

<p>Until next time ✌️</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Kind of odd that it’s Electron though, yeah? The ChatGPT flagship app is native, and it feels fantastic in comparison. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Jordan Morgan</name></author><category term="A.I." /><summary type="html"><![CDATA[Xcode just put out agentic support, baked right in. Let's take a look.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://swiftjectivec.com/assets/images/logo.png" /><media:content medium="image" url="https://swiftjectivec.com/assets/images/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Pay What You Want for The Best-in-Class iOS App Book Series</title><link href="https://swiftjectivec.com/Best-in-Class-iOS-App-Book-Series-Price-Update/" rel="alternate" type="text/html" title="Pay What You Want for The Best-in-Class iOS App Book Series" /><published>2026-01-28T00:00:00-06:00</published><updated>2026-01-28T00:00:00-06:00</updated><id>https://swiftjectivec.com/Best-in-Class-iOS-App-Book-Series-Price-Update</id><content type="html" xml:base="https://swiftjectivec.com/Best-in-Class-iOS-App-Book-Series-Price-Update/"><![CDATA[<p>First, let me get straight to it:</p>

<p>The <a href="https://bestinclassiosapp.com">Best-in-Class iOS App Book Series</a> is now <em>pay what you want</em>! Pick it up for a minimum of $10 to…whatever you want. Live now!</p>

<h3 id="so-why">So, Why?</h3>

<p>I debuted this book series many years ago now, back in <a href="https://www.swiftjectivec.com/Introducing-The-Best-in-Class-Book/">2021</a>!</p>

<div class="jekyll-twitter-plugin"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">🚀A Best-in-Class App: The Book 🚀<br /><br />It&#39;s happening, I&#39;m making this now! <br /><br />✅ In-depth book covering accessibility, core iOS tech, design, user experience and more.<br />✅ A companion app <br />✅ Bite sized screencasts<br /><br />Join the mailing list to follow along: <a href="https://t.co/Dc9AcfyUSL">https://t.co/Dc9AcfyUSL</a> <a href="https://t.co/S8sm1zPnNg">pic.twitter.com/S8sm1zPnNg</a></p>&mdash; Jordan Morgan (@JordanMorgan10) <a href="https://twitter.com/JordanMorgan10/status/1361707249366884354?ref_src=twsrc%5Etfw">February 16, 2021</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</div>

<p>I had just sold my first somewhat successful app, and figured I’d set out to do what I always wanted to do: write a book series of all that I knew about making iOS apps. The craft, the APIs, the design, the “don’t sleep on this little tweak” kind of stuff.</p>

<p>And, so, I did — though it was a bit grueling.</p>

<p>It took over two and a half years, and I released updates every two weeks during that time. But, like I would imagine those who finished a PhD or something, I was (and still am) extremely proud of the effort even though the effort basically swallowed me whole. I wrote over a thousand pages, hundreds of code samples, just as many image assets, kept the mailing list informed of progress, the whole gig. And since then, I’ve kept it up to date with free content drops.</p>

<p>What I couldn’t have imagined, at the time, was where we are today. Right now. The way we learn and do things, and most importantly — develop software, is changing at an unprecedented pace. AI and LLMs are changing our industry in real time. While there is certainly a place for books, YouTube tutorials, and similar content (I don’t think anybody wants those to go away) - I think the desire for a giant reference like mine is reducing a bit.</p>

<p>And that’s okay, but I also want to be realistic about things. And so, with updates slowing, I want to make it’s accessible to anyone who might want it.</p>

<h3 id="whats-next">What’s Next?</h3>

<p>I’ve also had several ideas floating around on what I could do with this content. With five, giant books full of iOS wisdom, plugging it into an LLM, custom GPT, maybe even an MCP, or putting it online digitally to highlight things, even edit it, etc — that could be fun. By far, the largest pain point is updating this thing. It takes hours to do for several reasons. An online-first home for it, with the option to download it still as it is today, would make the barrier to entry for updates much, <em>much</em> more doable for me.</p>

<p>I’m not really sure what I’ll do with it, but with over 100 updates since 2021, I’ve given this project a lot of me. I’m proud of it, and I’m also happy that whoever wanted it but couldn’t afford it, can do so now.</p>

<p>Until next time ✌️</p>]]></content><author><name>Jordan Morgan</name></author><category term="The Indie Dev Diaries" /><summary type="html"><![CDATA[The Best-in-Class iOS App Book Series is moving to a pay what you want model. Just ten bucks or more.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://swiftjectivec.com/assets/images/logo.png" /><media:content medium="image" url="https://swiftjectivec.com/assets/images/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The Shift</title><link href="https://swiftjectivec.com/The-Shift-Using-AI-As-A-Developer-Advocate-And-Engineer/" rel="alternate" type="text/html" title="The Shift" /><published>2026-01-18T00:00:00-06:00</published><updated>2026-01-18T00:00:00-06:00</updated><id>https://swiftjectivec.com/The-Shift-Using-AI-As-A-Developer-Advocate-And-Engineer</id><content type="html" xml:base="https://swiftjectivec.com/The-Shift-Using-AI-As-A-Developer-Advocate-And-Engineer/"><![CDATA[<p>It’s 2026 (in case you didn’t notice). Where have my traditional “Here’s what I did last year, here’s what I wanna do this year!” posts gone? I was able to achieve nearly all my goals in 2025 (the first time that’s happened), and I just sold <a href="https://www.swiftjectivec.com/introducing-elite-soccer-club/">Elite Soccer Club</a> to a friend, and I’ve got huge plans for 2026!</p>

<p>Why haven’t I written about any of it yet?</p>

<p>Well, because as a once ancient tweet once said, <em>everything just happens so much.</em> And in our industry, I don’t think I’ve ever witnessed so much happening.</p>

<p>I’ve known AI was helpful and it’s been a daily driver for me for some time now (ever the early adopter). But after seeing good ol’ <a href="https://x.com/steipete">Steipete</a> and <a href="https://x.com/Dimillian">Thomas</a> share more and more about their adventures, I started to dig in some more.</p>

<p>Skills, rules, plugins, MCPs, different models — I <em>went in</em>. And, coming out the other side, I’m not entirely certain what to think anymore. Excitement? Nervous? Pumped? All of it?</p>

<p>It’s all different now, but I do know that if you were already an engineer with experience before this AI boom, there has never been a better time in human history to build stuff.</p>

<p><strong>If you knew a <em>little</em> about a tech stack? Well, now you know a lot.</strong></p>

<p>I’ve been wondering what I am now with AI specifically. How would I describe myself? I don’t fancy myself as a vibe coder, I think we’ve attached a pejorative mindset to that term — one that won’t be shaken anytime soon. I think “Agentic Engineering” fits? Someone who knows what these things can do, and equally as important — how they could bring your house down if you don’t pay attention.</p>

<p>At work, I’ve been committing more code to our monorepo, our marketing site, cruising through Next.JS and Astro codebases, all the while doing a bunch of other stuff. And, it’s simply because…now I can.</p>

<p>Not even a year ago, Xcode stayed open while I cherry picked work over to chatGPT. Now, I have four terminals open - managing my little LLM minions to do my bidding. Sometimes, it feels like I code review for a living. But, I’m enjoying it?</p>

<p>My role has even been…impacted…if that’s the best word?, by AI. Developers are watching less YouTube tutorials and are relying on MCP servers for docs. The world is absolutely changing, and it is mind-boggling to be in it while it’s happening.</p>

<h3 id="wrapping-up">Wrapping Up</h3>
<p>When your industry shifts seemingly overnight, where does that put you? You either change with it, or open a coffee bar (Dude, I would <em>love</em> to open a coffee bar by the way).</p>

<p>I think we’re all a bit on edge, to some degree. The thought that, eventually, anybody will be able to make an app is exciting and a bit scary.</p>

<p>The barrier used to be building, and that barrier is diminishing. That means, logically, that the best idea, and its execution, its design and UX, how well it solves the problem - those will win more and more. That’s kinda, sorta, mostly true today, but the build aspect is still there right now. You could have the best taste, ideas on superior execution - but you might not be able to build the thing right now. That will change.</p>

<p>As such, where does that leave us?</p>

<p>I can’t say, but I do know that this is true, right now:</p>

<ul>
  <li>If you know how to code, then you are an absolute machine now. You can find the dumb stuff AI does, and you don’t treat it as a loose cannon. You guide it with tact, thinking, poise and a plan. It’s a force multiplier, not fertile ground for bugs.</li>
  <li>If you can design <em>and</em> code? Well, the world is yours…for now. All of these things will become more accessible in due time.</li>
</ul>

<p>In short? Go build, and do it now! We’ve got a major head start.</p>

<p>Until next time ✌️</p>]]></content><author><name>Jordan Morgan</name></author><category term="A.I." /><summary type="html"><![CDATA[What comes next? AI is changing everything.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://swiftjectivec.com/assets/images/logo.png" /><media:content medium="image" url="https://swiftjectivec.com/assets/images/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">A Month of Marketing: A Recap</title><link href="https://swiftjectivec.com/A-Month-of-Marketing-Recap/" rel="alternate" type="text/html" title="A Month of Marketing: A Recap" /><published>2025-12-17T00:00:00-06:00</published><updated>2025-12-17T00:00:00-06:00</updated><id>https://swiftjectivec.com/A-Month-of-Marketing-Recap</id><content type="html" xml:base="https://swiftjectivec.com/A-Month-of-Marketing-Recap/"><![CDATA[<p>Xcode is how you build a better product, marketing is how you build revenue - never vice-versa. That’s one thing I’ve learned over my previous <a href="https://www.swiftjectivec.com/A-Month-of-Marketing-for-Elite-Hoops/">month or marketing</a>. So, what happened?</p>

<div class="not-prose mt-8 -mb-4">
  <h3 class="bg-gradient-to-l from-blue-500 to-fuchsia-500 bg-clip-text text-transparent text-2xl font-bold leading-relaxed">
    Starting Numbers
  </h3>
</div>
<p><strong>Subscribers</strong>: 1,988 <br />
<strong>MRR</strong>: $7,633 <br />
<strong>Trials</strong>: 175 <br /></p>

<div class="not-prose mt-8 -mb-4">
  <h3 class="bg-gradient-to-l from-blue-500 to-fuchsia-500 bg-clip-text text-transparent text-2xl font-bold leading-relaxed">
    Ending Numbers
  </h3>
</div>
<p><strong>Subscribers</strong>: 2,220 <br />
<strong>MRR</strong>: $8,775 <br />
<strong>Trials</strong>: 193 <br /></p>

<p>So, looks good, right? Well, yeah. But also, I didn’t really do much of anything that I planned to do over the month 😅. The idea was to spend an inordinate of time posting things, engaging the community, writing blogs, and other similar growth stuff.</p>

<p>Elite Hoops is in-season, so I can just leave it alone right now and it’ll (thankfully!) grow. However, life happened over that month. Not to get too T.M.I., but I started seeing a therapist to help me work through some personal things, and that has been both incredibly helpful (good 😌!) <em>and</em> mentally exhausting (blah 😖!).</p>

<p>Regardless, I was able to do two impactful things:</p>

<ul>
  <li><strong>I worked with EVO Marketing</strong> and they did fantastic work. You can check them out <a href="https://evomarketing.co">here</a>. Not a sponsored post or anything, I just feel it was money well spent.</li>
  <li><strong>I shipped a lead gen tool</strong> called The Thompson Twin project. It’s live <a href="https://elitehoopsapp.com/tools/thompson-twins-workout">right here</a>.</li>
</ul>

<h3 id="evo">EVO</h3>

<p>EVO did a few things with me, and after reaching a price I felt good about — we all hopped into a shared Slack channel and went to work. This was my first time using a marketing agency, and I was pleasantly surprised. A few Superwall clients had used them for much larger projects, so I was initially thinking that indies probably weren’t their ideal customer. But, it worked out, and here’s what they did:</p>

<ul>
  <li>They made a <em>banger</em> Notion doc that summarized Elite Hoops as a product better than I could! I’ve been working on Elite Hoops since October 2023, and it’s insane seeing someone else whose mind works differently be able to articulate what you couldn’t (at least, articulate well). Who is my ICP? What kind of marketing speaks to them? How can you reach them?</li>
  <li>They made 20 video creatives for me.</li>
  <li>They taught me how to run paid ads.</li>
  <li>And, more importantly, they showed me how my current Meta campaign setup is, well, just very, very unoptimized.</li>
  <li>What’s my cost per install? Which ad works the best? LTV? When I was asked this stuff I just waved my hands and said I go on <em>vibes</em> bro.</li>
  <li>And while vibes got me to $9k MRR, it’s time to buckle up a bit more.</li>
</ul>

<p>Here’s a preview of their Notion package:</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/momt_1.jpeg" role="presentation" srcset="../assets/images/momt_1.jpeg" alt="Notion over Elite Hoops." /></p>

<p>Darren, Christian, and co. were great to work with and I’d recommend them to anyone trying to get things to the next level. A positive realization I had working through things with them was that I have clear traction, and people are converting. I just need to get the message out more to take things to that $40k MRR range.</p>

<p><strong>Takeaway:</strong> Having someone who does marketing as a job, and breathes it like you do development, is like…kind of a life hack for indies? It seems so obvious, but I have a recalcitrant view on marketing. I don’t enjoy it, but I know it’s vital. They did the things I just don’t want to do.</p>

<section class="bg-indigo-50 border-l-8 border-indigo-500 text-gray-800 rounded-md my-10 dark:text-gray-300 dark:bg-gray-600 dark:border-indigo-700 ">
    <div class="px-5 font-mono">
        <p class="pt-2 md:pt-4"><span class="text-purple-600 dark:text-purple-400">let</span> concatenatedThoughts = <span class="text-red-600">"""</span></p>
        <p class="text-gray-600 dark:text-gray-300 text-sm md:text-base">Also, you either die an indie, or live long enough to install the Facebook SDK to get install attribution. So it goes!</p>
        <p class="pb-2 md:pb-4 text-gray-600 text-base"><span class="text-red-600">"""</span></p>
    </div>
</section>

<h3 id="the-thompson-twins-project">The Thompson Twins Project</h3>

<p>Next, I shipped the <a href="https://elitehoopsapp.com/tools/thompson-twins-workout">Thompson Twins project</a>:
<img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/momt_2.jpeg" role="presentation" srcset="../assets/images/momt_2.jpeg" alt="The Thompson Twins project on Elite Hoops." /></p>

<p>If you aren’t familiar with Amen and Asur Thompson, they are twin guards in the NBA who allegedly did this insane workout growing up. So, I thought it would be fun to codify it, and put it in Elite Hoops. It turned out looking like this:</p>

<ul>
  <li>A web component, linked above.</li>
  <li>You give me an email, I give you the workout.</li>
  <li>Because the workout is insane, I decided to split it up into a 7 day progression - getting closer to the full routine each day.</li>
  <li>The .pdf turned out nice, I just designed it all in Sketch.</li>
  <li>And, it’s in the iOS app too (app review pending).</li>
</ul>

<p>I haven’t started marketing this yet, but the email blast and socials will go out soon. The idea is to try and tap into some virality and bring people into the Elite Hoops ecosystem.</p>

<p><strong>Takeaway:</strong> These little free tools as a marketing vehicle have worked well for me in the past. Plus. I just love working with next.js - it’s fun! It’s good to have fun, but time will tell if this was worth it or not.</p>

<h3 id="now-what">Now What?</h3>

<p>The last month was a learning lesson. Thankfully, I spent money on people to help me grow Elite Hoops. Those paid ads are about to start running next week, so that’ll be exciting to see how it plays out. Going forward, I’m going to try and just do a simple “Mon/Tue” marketing flow, then development on the other days. It may seem rigid, but for me, it simply reminds me to do it.</p>

<p>And, there’s plenty to do:</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/momt_3.jpeg" role="presentation" srcset="../assets/images/momt_3.jpeg" alt="Craft doc over marketing ideas for Elite Hoops." /></p>

<p>Until next time ✌️</p>]]></content><author><name>Jordan Morgan</name></author><category term="The Indie Dev Diaries" /><summary type="html"><![CDATA[After spending a month on marketing, here's what I learned. And more importantly, what comes next.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://swiftjectivec.com/assets/images/logo.png" /><media:content medium="image" url="https://swiftjectivec.com/assets/images/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">A Month of Marketing for Elite Hoops</title><link href="https://swiftjectivec.com/A-Month-of-Marketing-for-Elite-Hoops/" rel="alternate" type="text/html" title="A Month of Marketing for Elite Hoops" /><published>2025-11-04T00:00:00-06:00</published><updated>2025-11-04T00:00:00-06:00</updated><id>https://swiftjectivec.com/A-Month-of-Marketing-for-Elite-Hoops</id><content type="html" xml:base="https://swiftjectivec.com/A-Month-of-Marketing-for-Elite-Hoops/"><![CDATA[<p>I’ll keep it short — I’m going to focus on doing a month straight of marketing for Elite Hoops. Xcode will only open if it helps me market something. I’ll try to update this post for the next 30 days on what I’ve tried. Check back each day to see what I’m up to. Think of this post as a live journal.</p>

<h3 id="wednesday-november-5th">Wednesday, November 5th</h3>
<ul>
  <li>Researched existing social formats to discover videos I could make for paid ads.</li>
  <li>I found about four or five that I think I could turn around fairly quick.</li>
  <li>One question I have on these - do I go with boosted posts, or actual paid ads?</li>
  <li>I’m also looking at blog posts that are evergreen to help with SEO efforts, the Elite Hoops website will be critical moving forward.</li>
</ul>

<h3 id="thursday-november-6th">Thursday, November 6th</h3>
<ul>
  <li>One ephinany I had — my app is seasonal, and its season right now. So during my seasonal months, I need to triple down on marketing, and in the off-season I should triple down on feature development. I think not crossing those wires is smart.</li>
  <li>I have a meeting with a marketing agency Friday, exicted to see what that brings.</li>
  <li>One idea I have is to piggyback off of viral workouts that NBA stars used to do, maybe I could build those into Elite Hoops somehow.</li>
</ul>

<h3 id="friday-november-7th">Friday, November 7th</h3>
<ul>
  <li>Hired a marketing agency, a 30 day agreement. I’m excited about this - it takes the load off of me for creating content. They’re going to do it.</li>
  <li>That also means I’m a bit free to do other stuff, should it be content marketing? Blogs? Plays on YouTube? Where should I go?</li>
  <li>I’m excited about that viral NBA workout idea I had, that’s pulling me in — so I’m going to explore that starting tomorrow.</li>
</ul>

<h3 id="saturday-november-8th">Saturday, November 8th</h3>
<ul>
  <li>Absolutely nothing, I was at kid’s sporting events all day.</li>
</ul>

<h3 id="sunday-november-9th">Sunday, November 9th</h3>
<ul>
  <li>And nothing again, I watched football all day and it was great.</li>
</ul>

<h3 id="monday-november-10th">Monday, November 10th</h3>
<ul>
  <li>The marketing agency I’m working with is going to produce 20 videos for me, so content wise I’ll be posting those.</li>
  <li>Since that frees me up, I’m going with this “Thompson Twins” workout idea.</li>
  <li>That’ll include a blog post, another “free tool” I think on the Elite Hoops’ website, and I need to add the drills to the app itself.</li>
  <li>Then, I’m thinking an email marketing campaign, along with reels I have around this to promote. This is lead-gen, since it’ll be free.</li>
</ul>

<p>Also, here’s how I’m kind of thinking about these 30 days in terms of marketing:</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/mom_plan.png" role="presentation" srcset="../assets/images/mom_plan.png" alt="Month of marketing gameplan." /></p>

<h3 id="tuesday-november-11th">Tuesday, November 11th</h3>
<ul>
  <li>I’ve got the “Thompson Twins Workout” idea all fleshed out now. It’s a free tool on the website, and a template workout in the app.</li>
  <li>The website version will actually be a bit more fleshed out, the workout is insane. So I have a 7 day plan .pdf to work up to it (email gated).</li>
  <li>First up, I need to make the actual .pdf…and this is killing me. It’s soul crushing work making it myself, I would be faster doing it in SwiftUI.</li>
</ul>

<h3 id="wednesday-november-12th">Wednesday, November 12th</h3>
<ul>
  <li>I added some more quotes to the basketball quotes post.</li>
  <li>I’m slogging through this .pdf…I <em>hate</em> working on it and I have no idea why. I think because it’s hard for me to make it pretty, easily.</li>
  <li>But, it has to be done because the rest of the project hinges on it.</li>
</ul>

<h3 id="thursday-november-13th">Thursday, November 13th</h3>
<ul>
  <li>If you were wondering, I still hate this .pdf.</li>
  <li>BUT also, I nailed down the design, and I’m almost done with it! It’s seven pages, so even though I had the content, I needed the design to, well, perfect. It’s the centerpiece of the whole package.</li>
  <li>The marketing agency has nailed down formats and creators, so that should start bearing fruit soon.</li>
</ul>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/tt_pdf.jpeg" role="presentation" srcset="../assets/images/tt_pdf.jpeg" alt="The .pdf final design for the Thompson Twins project." /></p>

<h3 id="friday-november-14th">Friday, November 14th</h3>
<ul>
  <li>Game planned with my marketing agency, we should have videos next week.</li>
  <li>I updated some blog posts which are starting to rank and drive good SEO results.</li>
  <li>I had to rework some of the workouts for the Thompson Twins workout, but I’m happy with it now.</li>
</ul>

<h3 id="saturday-november-15th">Saturday, November 15th</h3>
<ul>
  <li>Did nothing!</li>
</ul>

<h3 id="sunday-november-16th">Sunday, November 16th</h3>
<ul>
  <li>And then I did nothing again!</li>
</ul>

<h3 id="monday-november-17th">Monday, November 17th</h3>
<ul>
  <li>Everyone, an announcement: I HAVE FINISHED THE .pdf! What a battle, seven whole pages. But it turned out nice, and I had to nail it.</li>
  <li>Met with the marketing agency, reviewed formats — we’re ready to record!</li>
  <li>Started in on the web dev side of things, which shouldn’t take too long to finish. Famous last words, I guess.</li>
</ul>

<h3 id="tuesday-november-18th">Tuesday, November 18th</h3>
<ul>
  <li>I put together the other assets for the landing page. Now just getting everything in place, and getting the design to look right.</li>
  <li>The copy is…not great, currently. I really need to sell this a bit better, so I’m working through that.</li>
  <li>Marketing agency has been great, they delivered a massive game plan on copy, types of videos, my ICP, just tons of stuff.</li>
</ul>

<h3 id="wednesday-november-19th---november29th">Wednesday, November 19th - November29th</h3>
<ul>
  <li>Well, not to get too personal, but…<em>life stuff</em> happened recently. I had to put things down since I had zero mental energy lately.</li>
  <li>But, the marketing agency is paying off! They’ve delivered over ten video ads, with copy, targeting advice and more for me to try.</li>
  <li>Next, they are delivering UGC content.</li>
  <li>Now that I’m back in the saddle, I’ve picked up The Thompson Twins project. Today, I’m getting the landing page design finished.</li>
</ul>

<h3 id="sunday-november-30th">Sunday, November 30th</h3>
<ul>
  <li>The Thompson Twins project is live! Check it out <a href="https://elitehoopsapp.com/tools/thompson-twins-workout">here</a>!</li>
  <li>Tomorrow, I meet with the marketing agency about posting strategies for the content they’ve made me.</li>
</ul>

<h3 id="monday-december-1st---december-4th">Monday, December 1st - December 4th</h3>
<ul>
  <li>Wrapping up! The marketing agency and I had a wrap up call.</li>
  <li>I have clear next steps to try in terms of ads.</li>
  <li>And we’re done! Look for a wrap up post coming tomorrow.</li>
</ul>

<p>Until next time ✌️</p>]]></content><author><name>Jordan Morgan</name></author><category term="The Indie Dev Diaries" /><summary type="html"><![CDATA[For the next 30 days, Xcode is closed. And marketing channels are open.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://swiftjectivec.com/assets/images/logo.png" /><media:content medium="image" url="https://swiftjectivec.com/assets/images/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Opt for Localized Strings</title><link href="https://swiftjectivec.com/Using-Localized-String-For-Easy-Localization-Refactoring/" rel="alternate" type="text/html" title="Opt for Localized Strings" /><published>2025-10-22T00:00:00-05:00</published><updated>2025-10-22T00:00:00-05:00</updated><id>https://swiftjectivec.com/Using-Localized-String-For-Easy-Localization-Refactoring</id><content type="html" xml:base="https://swiftjectivec.com/Using-Localized-String-For-Easy-Localization-Refactoring/"><![CDATA[<p>One of my goals this year was to localize my soccer app into German, French and Spanish. With the nascent String Catalogs, and <a href="https://developer.apple.com/videos/play/wwdc2025/225?time=451">Xcode’s 26 on-device inference engine for creating comments</a> about what each String represents, it felt like the time was right.</p>

<p>Ah, the time was right, but my code was not. I was using plain String types in <em>a lot</em> of places, and a String catalog won’t pick those up for translation:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">enum</span> <span class="kt">AppTab</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="kt">CaseIterable</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">teams</span><span class="p">,</span> <span class="n">drills</span><span class="p">,</span> <span class="n">practices</span>
<span class="p">}</span>

<span class="c1">// Later on, a simplified example...</span>
<span class="kt">ForEach</span><span class="p">(</span><span class="kt">AppTab</span><span class="o">.</span><span class="n">allCases</span><span class="p">)</span> <span class="p">{</span> <span class="n">tab</span> <span class="k">in</span> 
    <span class="kt">Text</span><span class="p">(</span><span class="n">tab</span><span class="o">.</span><span class="n">rawValue</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This is easy to miss, because SwiftUI does a fantastic job and opting you into using localizable String types, even if you don’t realize it:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">Text</span><span class="p">(</span><span class="s">"Make localizing your app easy!"</span><span class="p">)</span>
</code></pre></div></div>

<p>Under the hood, that string is a <code class="language-plaintext highlighter-rouge">LocalizedStringKey</code>, which means a String Catalog will pick it up for translation:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">init</span><span class="p">(</span>
    <span class="n">_</span> <span class="nv">key</span><span class="p">:</span> <span class="kt">LocalizedStringKey</span><span class="p">,</span>
    <span class="nv">tableName</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span> <span class="o">=</span> <span class="kc">nil</span><span class="p">,</span>
    <span class="nv">bundle</span><span class="p">:</span> <span class="kt">Bundle</span><span class="p">?</span> <span class="o">=</span> <span class="kc">nil</span><span class="p">,</span>
    <span class="nv">comment</span><span class="p">:</span> <span class="kt">StaticString</span><span class="p">?</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="p">)</span>
</code></pre></div></div>

<p>Going forward, I’ve started writing String variables, parameters, and anything else that’ll show in a UI (which, well, Strings tend to do…all the time) using <code class="language-plaintext highlighter-rouge">LocalizedStringKey</code> — there’s no code changes you need to make when you swap this with a String type, plus you get the String Catalog support:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// From this</span>
<span class="kd">struct</span> <span class="kt">AnotherView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">headerText</span><span class="p">:</span> <span class="kt">String</span> <span class="c1">// &lt;-- Won't show in String Catalog</span>
    
    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">Text</span><span class="p">(</span><span class="n">headerText</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>


<span class="c1">// To this</span>
<span class="kd">struct</span> <span class="kt">AnotherView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">headerText</span><span class="p">:</span> <span class="kt">LocalizedStringKey</span> <span class="c1">// &lt;-- Will show in String Catalog</span>
    
    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">Text</span><span class="p">(</span><span class="n">headerText</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

</code></pre></div></div>

<p>To follow up on the first example:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">enum</span> <span class="kt">AppTab</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="kt">CaseIterable</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">teams</span> <span class="o">=</span> <span class="kt">String</span><span class="p">(</span><span class="nv">localized</span><span class="p">:</span> <span class="s">"Teams"</span><span class="p">),</span> <span class="n">drills</span> <span class="o">=</span> <span class="kt">String</span><span class="p">(</span><span class="nv">localized</span><span class="p">:</span> <span class="s">"Drills"</span><span class="p">),</span> <span class="n">practices</span> <span class="o">=</span> <span class="kt">String</span><span class="p">(</span><span class="nv">localized</span><span class="p">:</span> <span class="s">"Practices"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The same goes if you have interpolated Strings, using <code class="language-plaintext highlighter-rouge">String(localized:comment:)</code> does the trick:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// From this</span>
<span class="k">let</span> <span class="nv">result</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">didGeneratePlan</span> <span class="p">?</span> <span class="s">"Practice plan ready!"</span> <span class="p">:</span> <span class="s">"Failed to generate plan."</span>

<span class="c1">// To this</span>
<span class="k">let</span> <span class="nv">result</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">didGeneratePlan</span> <span class="p">?</span> <span class="kt">String</span><span class="p">(</span><span class="nv">localized</span><span class="p">:</span> <span class="s">"Practice plan ready!"</span><span class="p">)</span> <span class="p">:</span> <span class="kt">String</span><span class="p">(</span><span class="nv">localized</span><span class="p">:</span> <span class="s">"Failed to generate plan."</span><span class="p">)</span>
</code></pre></div></div>

<section class="bg-indigo-50 border-l-8 border-indigo-500 text-gray-800 rounded-md my-10 dark:text-gray-300 dark:bg-gray-600 dark:border-indigo-700 ">
    <div class="px-5 font-mono">
        <p class="pt-2 md:pt-4"><span class="text-purple-600 dark:text-purple-400">let</span> concatenatedThoughts = <span class="text-red-600">"""</span></p>
        <p class="text-gray-600 dark:text-gray-300 text-sm md:text-base">Notice how I didn't use the `comment:` parameter in that last example? I found that Xcode's automatic generation was so good, it was making better comments that I did.</p>
        <p class="pb-2 md:pb-4 text-gray-600 text-base"><span class="text-red-600">"""</span></p>
    </div>
</section>

<h3 id="my-localization-stack">My Localization “Stack”</h3>

<p>It took the better part of my side project time last week, but I was able to complete a full translation to three new languages in <a href="https://elitefootballclubapp.com">Elite Soccer Club</a>. It’s rolling out in v1.2.0 once App Review gives it its blessing, along with lineup sharing, <a href="https://www.swiftjectivec.com/eight-fun-swiftui-details-in-practice-planner/">Elite Hoops’ popular practice planner</a> but retooled for soccer, and quite a bit more.</p>

<p>Here’s what I used to get my “v1” of localizations done:</p>

<ul>
  <li>I converted all <code class="language-plaintext highlighter-rouge">String</code> types to <code class="language-plaintext highlighter-rouge">LocalizedStringKey</code>, and any inline Strings to <code class="language-plaintext highlighter-rouge">String(localized:)</code>.</li>
  <li>I created String Catalogs, and downloaded the on-device model to create comments.</li>
  <li>Following <a href="https://danielsaidi.com/blog/2025/06/08/using-ai-and-cursor-to-localize-xcode-string-catalogs">Daniel Saidi’s</a> fantastic blog post, I used Cursor and Claude to translate over 3,000 items.</li>
  <li>Then, using <a href="https://butterkit.app">ButterKit</a>, I paid the easiest $30 of my life, plugged in my OpenAI key, and had it translate all of the screenshot text.</li>
  <li>
    <p>Superwall automatically translated <em>all</em> of my paywalls to three languages in under 30 seconds. This feature is absolutely insane.
<img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/translate_pw.jpeg" role="presentation" srcset="../assets/images/translate_pw.jpeg" alt="Translating paywalls in Superwall." /></p>
  </li>
  <li>And finally, I died inside while updating 6,000,000 things in App Store Connect — which would randomly lose images I uploaded constantly. This was not fun.</li>
</ul>

<p>I feel like that gave me an incredible start, and a sign of the times that I could even do all of this within a week. While I am completely sure some of the translations won’t land, it’s better than not having anything. I plan on iterating when I get feedback to make things better, but also - Xcode’s comment generation surely helped AI translation since it had the extra context. I augmented the prompt in the blog linked above to make sure they were considered.</p>

<p>Until next time ✌️</p>]]></content><author><name>Jordan Morgan</name></author><category term="Foundation" /><summary type="html"><![CDATA[Even if you aren't localizing a codebase, or even plan to soon, you should try to use localized String types. Here's an example of how it can save time.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://swiftjectivec.com/assets/images/logo.png" /><media:content medium="image" url="https://swiftjectivec.com/assets/images/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Learnable, Memorable, Accessible</title><link href="https://swiftjectivec.com/Creating-Custom-Controls-SwiftUI-Learnable-Memorable-Accessibile/" rel="alternate" type="text/html" title="Learnable, Memorable, Accessible" /><published>2025-10-03T00:00:00-05:00</published><updated>2025-10-03T00:00:00-05:00</updated><id>https://swiftjectivec.com/Creating-Custom-Controls-SwiftUI-Learnable-Memorable-Accessibile</id><content type="html" xml:base="https://swiftjectivec.com/Creating-Custom-Controls-SwiftUI-Learnable-Memorable-Accessibile/"><![CDATA[<p>Maybe it’s just me, but it seems like we’re seeing quite a bit more custom controls in iOS apps lately. My theory? It’s a direct result of SwiftUI, wherein its composable nature makes them easier to architect when contrasted to UIKit. Override <code class="language-plaintext highlighter-rouge">init(frame:)</code>? Nah, just toss a <code class="language-plaintext highlighter-rouge">VStack</code> out there and add what you need.</p>

<p>The barrier to entry is lower, but the risk for tomfoolery has scaled linearly.</p>

<p>Thankfully, Apple has done a <em>phenomenal</em> job of keeping these controls accessible. Most of it, true to form, “just works” — but some of it doesn’t. So here’s a quick guide to follow, should you consider a custom control. Our subject matter for this post? This little toggle-segment guy found in <a href="https://itunes.apple.com/us/app/id6473840226">Alyx</a>:</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/cc_1.jpeg" role="presentation" srcset="../assets/images/cc_1.jpeg" alt="A segment control customized in Alyx." /></p>

<p>For custom controls, there are three rules I try to follow. Each custom control should be:</p>

<ol>
  <li>
    <p><strong>Learnable:</strong> If something is not obvious to use, people will not use it at all.</p>
  </li>
  <li>
    <p><strong>Memorable:</strong> If something has no obvious reason to be used in lieu of the system control, reconsider making it.</p>
  </li>
  <li>
    <p><strong>Accessible:</strong> If something can’t be used by everyone, then it probably shouldn’t be shipped to anyone.</p>
  </li>
</ol>

<p>So, back to my toggle guy.</p>

<h3 id="learnable">Learnable</h3>
<p>Is this learnable? I think so, because it passes the visual inspection test. If someone looks at it, they are likely to understand why it’s there and what it might do (regardless as to whether or not they are privy to the tech scene). Here, they are likely to think, “This seems like a toggle.” That’s not by accident, as a <code class="language-plaintext highlighter-rouge">Picker</code> with the <code class="language-plaintext highlighter-rouge">segmented</code> style is a prevalent SwiftUI control, whose roots go back to <code class="language-plaintext highlighter-rouge">UISegmentedControl</code> (a control that’s been around so long iOS wasn’t even called iOS when it first shipped, it was iPhoneOS).</p>

<p>Like other controls, if it works, then it inherently becomes more flexible, too. I have another similar variant of the same control I use to toggle dates, it’s mostly the same but just a <em>smidge</em> tinier:</p>

<p><img class="lozad mx-auto max-w-full h-auto shadow-2xl rounded-lg h-18" data-src="../assets/images/cc_2.jpeg" role="presentation" srcset="../assets/images/cc_2.jpeg" alt="The same control used in another context." /></p>

<p>There is a tolerance scale you have to weigh here, and finding the balance on it doesn’t come naturally to a lot of us. It’s easy to make a custom control <em>because you can</em>, it’s not exactly hard anymore. Always pump the brakes first, and ask yourself if the control will be understood at first glance. If the cognitive load to understand it is high, then the reason to ship it should be low.</p>

<h3 id="memorable">Memorable</h3>
<p>But (<em>and there’s always a "but", isn’t there?</em>)!</p>

<p>There is, of course, a spectrum here — because part of the joy of custom controls <em>can</em> be discovery. If the intent is to drive home some selective emphasis and joy, I tend to think that’s a completely legitimate reason to make a custom control. We can wax poetic about how boring software is now, but…actually — yeah, let’s keep doing that! Adding a little splash of creativity to your app can be endearing, and it can also make it <em>memorable</em>.</p>

<p>There are different ways to be memorable, though, and many of them have nothing (at least, directly) to do with jolts of pure creativity. For example, when Loren Brichter created the pull to refresh UX, I assume that it wasn’t exactly created to be splashy, nor was it the product of a need to express a creative outlet, <em>it just made more sense than we had been doing</em>. The rest, is of course, cemented in history on your phone right now. We all pull to refresh.</p>

<p>As such, my decision to make the custom toggle in Alyx was a creative one. I wanted to reinforce its branding, the roundy-ness, bouncy and playful tone of the app, that nothing is really <em>that</em> serious here. And, it was just a gut call to assume that this one was better than the stock one for my use case:</p>

<p><br /></p>
<div class="image-comparison-wrapper rounded-xl not-prose">
  <div class="relative w-full overflow-hidden image-comparison-slider" id="imageComparisonSlider">
    <img src="/assets/images/cc_3.jpeg" alt="Image 1" class="w-full h-auto rounded-xl" />
    <div class="absolute top-0 left-0 w-full h-full overflow-hidden" id="imageWrapper">
      <img src="/assets/images/cc_1.jpeg" alt="Image 2" class="absolute top-0 left-0 w-full h-full object-cover rounded-xl" />
    </div>
    <div class="slider-handle" id="sliderHandle">
      <div class="slider-button">
        <div class="flex">
            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-4 w-4">
                <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
              </svg>
              <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-4 w-4">
                <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
              </svg>             
        </div>
      </div>
    </div>
    
      <div class="caption-container rounded-xl">
        <div class="caption caption-left sjc-prose" id="caption1">My version</div>
        <div class="caption caption-right sjc-prose" id="caption2">Stock version</div>
      </div>
    
  </div>
</div>

<style>
  .image-comparison-wrapper {
    position: relative;
    user-select: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
  }

  .image-comparison-slider {
    position: relative;
    z-index: 0;
  }

  .slider-handle {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 4px;
    background: white;
    cursor: ew-resize;
    z-index: 10;
  }

  .slider-button {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 40px;
    height: 40px;
    background: white;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .slider-icon {
    width: 24px;
    height: 24px;
    color: #333; /* Adjust color as needed */
  }

  .caption-container {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    display: flex;
    justify-content: space-between;
    padding: 10px;
    background: rgba(255, 255, 255, 0.7);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
  }

  .caption {
    padding: 10px 15px;
    color: black;
    font-size: 14px;
    max-width: 45%;
    overflow: hidden;
    transition: opacity 0.3s ease;
  }

  .caption-left {
    text-align: left;
  }

  .caption-right {
    text-align: right;
  }
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
  const slider = document.getElementById('imageComparisonSlider');
  const sliderHandle = document.getElementById('sliderHandle');
  const imageWrapper = document.getElementById('imageWrapper');
  const caption1 = document.getElementById('caption1');
  const caption2 = document.getElementById('caption2');
  let isDragging = false;
  let currentX;

  function updateSliderPosition() {
    if (!isDragging && currentX === undefined) return;

    const sliderRect = slider.getBoundingClientRect();
    let position = (currentX - sliderRect.left) / sliderRect.width;
    position = Math.max(0, Math.min(position, 1));

    sliderHandle.style.left = `${position * 100}%`;
    imageWrapper.style.clipPath = `inset(0 ${100 - position * 100}% 0 0)`;

    // Update caption opacity
    if (caption1 && caption2) {
      const minOpacity = 0.20;
      const maxOpacity = 1;
      const opacityRange = maxOpacity - minOpacity;

      let opacity1 = minOpacity + opacityRange * (1 - position);
      let opacity2 = minOpacity + opacityRange * position;

      caption1.style.opacity = opacity1;
      caption2.style.opacity = opacity2;
    }

    requestAnimationFrame(updateSliderPosition);
  }

  function startDragging(e) {
    isDragging = true;
    currentX = e.clientX || e.touches[0].clientX;
    requestAnimationFrame(updateSliderPosition);
  }

  function stopDragging() {
    isDragging = false;
    currentX = undefined;
  }

  function onMove(e) {
    if (!isDragging) return;
    currentX = e.clientX || e.touches[0].clientX;
  }

  sliderHandle.addEventListener('mousedown', startDragging);
  sliderHandle.addEventListener('touchstart', startDragging);

  document.addEventListener('mousemove', onMove);
  document.addEventListener('touchmove', onMove, { passive: false });

  document.addEventListener('mouseup', stopDragging);
  document.addEventListener('touchend', stopDragging);

  slider.addEventListener('click', function(e) {
    currentX = e.clientX;
    requestAnimationFrame(updateSliderPosition);
  });

  // Initialize slider position
  currentX = slider.getBoundingClientRect().left + slider.offsetWidth / 2;
  requestAnimationFrame(updateSliderPosition);
});
</script>

<p><br /></p>

<h3 id="accessible">Accessible</h3>
<p>Of course, if it’s not accessible then you’ll run into a whole host of issues. Empathy is the best teacher, and it wasn’t until I personally met someone who relied on accessibility features on their phone that I truly grasped how critical it is to consider. While you can easily say it’s the right thing to do, I think that’s an obvious argument to make. <em>Of course it is!</em></p>

<p>Beyond that, custom controls that fully support all accessibility contexts also have an air of craftsmanship to them that not everyone is willing to achieve. And, it’s so easy to do that now! Apple has a <strong>killer</strong> API for custom controls and accessibility, <code class="language-plaintext highlighter-rouge">.accessibilityRepresentation</code>. This lets you vend an entirely different control to the accessibility engine in place of the one that you’ve made.</p>

<p>Why is that critical? Because you can pass off Apple’s controls! And guess what? They’ve thought about more accessibility edge cases than you or I have. So, here, that could look something like this:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">HStack</span> <span class="p">{</span>
    <span class="n">theControl</span>
        <span class="o">.</span><span class="n">accessibilityRepresentation</span> <span class="p">{</span>
            <span class="kt">Picker</span><span class="p">(</span><span class="s">""</span><span class="p">,</span> <span class="nv">selection</span><span class="p">:</span> <span class="n">$inputMode</span><span class="p">)</span> <span class="p">{</span>
                <span class="kt">ForEach</span><span class="p">(</span><span class="kt">PresentationInput</span><span class="o">.</span><span class="n">allCases</span><span class="p">)</span> <span class="p">{</span> <span class="n">mode</span> <span class="k">in</span>
                    <span class="kt">Button</span> <span class="p">{</span>
                        <span class="nf">update</span><span class="p">(</span><span class="nv">to</span><span class="p">:</span> <span class="n">mode</span><span class="p">)</span>
                    <span class="p">}</span> <span class="nv">label</span><span class="p">:</span> <span class="p">{</span>
                        <span class="kt">Image</span><span class="p">(</span><span class="nv">systemName</span><span class="p">:</span> <span class="n">mode</span><span class="o">.</span><span class="n">glyph</span><span class="p">)</span>
                    <span class="p">}</span>
                <span class="p">}</span>
            <span class="p">}</span>
            <span class="o">.</span><span class="nf">pickerStyle</span><span class="p">(</span><span class="o">.</span><span class="n">segmented</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now, the accessibility engine will see Apple’s far superior accessibility implementation. When Apple shipped this change, it became immediately obvious that this was the best way to handle similar situations — I couldn’t believe A) I had never thought of it, and B) it didn’t ship with SwiftUI 1.0. It’s a literal cheat code for a good accessibility outcome.</p>

<p>Even though I just mentioned there is a little hint of craftsmenship to fantastic accessibility support in custom controls, on second thought — the APIs, SwiftUI and UIKit have become so accessible by default that it’s almost harder to make something <em>not</em> accessible. That’s a good place to be.</p>

<p>So that’s my thought process. Learnable, memorable and accessible. If your custom control passes that smell test, then you’re probably heading down a good path.</p>

<p>Until next time ✌️</p>]]></content><author><name>Jordan Morgan</name></author><category term="SwiftUI" /><summary type="html"><![CDATA[Creating custom controls is something that's just flat out fun. But, I believe, there's an objectively right way to do it.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://swiftjectivec.com/assets/images/logo.png" /><media:content medium="image" url="https://swiftjectivec.com/assets/images/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>