<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Freek on Medium]]></title>
        <description><![CDATA[Stories by Freek on Medium]]></description>
        <link>https://medium.com/@frzi?source=rss-619798037cc3------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*41Fcuxvw7tJGE-qY.jpg</url>
            <title>Stories by Freek on Medium</title>
            <link>https://medium.com/@frzi?source=rss-619798037cc3------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 16 Jun 2026 22:38:33 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@frzi/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Image placeholders in pure CSS — or: Defying gods with math and color]]></title>
            <link>https://frzi.medium.com/lqip-css-73dc6dda2529?source=rss-619798037cc3------2</link>
            <guid isPermaLink="false">https://medium.com/p/73dc6dda2529</guid>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[lqip]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[css]]></category>
            <dc:creator><![CDATA[Freek]]></dc:creator>
            <pubDate>Mon, 18 Aug 2025 16:33:00 GMT</pubDate>
            <atom:updated>2025-12-18T19:43:36.282Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WwHoLzGdp2B6LO_7mjQskA.png" /></figure><h3>Image placeholders in pure CSS — or: Defying gods with math and color</h3><h4>Creating an LQIP in pure CSS using nothing but math, color and a bit of shenanigans</h4><blockquote><strong><em>😎 </em>Non-members: </strong>Read for free <a href="https://frzi.medium.com/lqip-css-73dc6dda2529?source=friends_link&amp;sk=3107d6a7df8a53d8df6e61f1b314e141">HERE</a>!</blockquote><h3><strong>Behold</strong></h3><p>… the absolute wickedness one can achieve with CSS. With just a single hex code…</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*W5cz2QGqjlBkvHPCbgm8cA.png" /></figure><p>… we can create this:</p><figure><img alt="A 3-colored mesh gradient" src="https://cdn-images-1.medium.com/max/1024/1*xXYdlv51r08isCM8sqsDQA.jpeg" /></figure><p>It may not look impressive, but that’s a 3-color “mesh gradient” image to act as an LQIP (Low Quality Image Placeholder).</p><p>A while back <a href="https://leanrada.com/">Lean Rada</a> stunned the web development community with an ingenious trick: <a href="https://leanrada.com/notes/css-only-lqip/">A CSS-only LQIP implementation</a>. With a single value between -999,999 and 999,999 they create a blurry low-res representation of an image as a placeholder, entirely via CSS. I implore you to read their article to have your brain wrinkled because I cannot overstate how brilliant this is.</p><p>This inspired me to explore other avenues of implementing LQIPs, purely in CSS. Not to create something <em>better </em>(like hell I could), but to harness CSS’ power <em>differently </em>and see what else is possible<em>.</em></p><p>Before we get into the technical nitty gritty, we first have to talk about LQIPs as a concept and the benefits of implementing LQIPs via CSS. If you’re already well up to speed or just don’t care, you can ⌘-F (or Ctrl-F) the 🧪 emoji to jump to the code and the science behind it all!</p><p><a href="https://frzi.github.io/lqip-css">Don’t forget to check the implementation in action over here</a>.</p><h3>What the doggone flipping heck is an LQIP?</h3><p>First of all, watch your language. Second, you’ve undoubtedly seen them before! You visit a site or open an app, but before the images are fully downloaded you are presented with an abstract representation in their place. This is called an LQIP: a Low Quality Image Placeholder. And they come in different forms. Like a single color (often the <a href="https://www.color-meanings.com/dominant-recessive-colors/">dominant color</a>), a simple gradient of two colors, or perhaps a very blurry low-resolution version of the original image.</p><figure><img alt="A photo of a seal and 3 forms of a LQIPs. From left to right: dominant color, two color linear gradient and a blurry low res bitmap" src="https://cdn-images-1.medium.com/max/1024/1*8MOakcHNdwYSnOj9hx2Evg.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@lachlancormie?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Lachlan</a> on <a href="https://unsplash.com/photos/sea-lion-on-brown-sand-during-daytime-DJgNuM5NEKE?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></figcaption></figure><p>They’re a fancy feature that improve the UX ever so slightly: It prevents your site/app from looking barren when opening; they can help users potentially identify images early, but most importantly: they’re damn <em>exquisite </em>and show that you — the developer — care about the details. 😎</p><p>That said, they’re not as common as we probably think they are.</p><p>Implementing LQIPs requires some forethought on the backend side of things. Images have to be analysed, an LQIP has to be encoded into some form of “representation”, this “representation” then has to be stored somewhere for later use (e.g. a database). Finally, the frontend needs to receive these “representations” (preferably in the earliest payload) and generate the placeholder before downloading and showing the original image.</p><p>The second challenge is the fact an LQIP is <em>more </em>data you have to send to the frontend… And as a developer you want to avoid the point of diminishing returns: where the bandwidth (and time) of the extra data could’ve been better spent downloading the original files instead.</p><p>This is where the fight truly begins: finding an LQIP implementation that performs well and requires as little data as possible. Due to this extra hassle some developers opt to simply use <a href="https://www.hostinger.com/tutorials/what-is-progressive-jpeg-images">Progressive JPEGs</a> instead. Which isn’t a replacement for LQIPs <em>entirely</em>, but does present images to the user faster for an improved UX.</p><h3>Different LQIP implementations</h3><p>Perhaps the most common and naive way to implement LQIPs is by creating a low-res image of the original and have the frontend download it before downloading the original (or simultaneously).</p><p>Seems simple enough. But the drawback of this is the frontend will have to make <em>more </em>requests to the backend to download <em>more </em>files, which in turn is <em>more</em> data going back and forth.</p><p>Alternatively, the low-res image can be embedded inline into the HTML/CSS as a base64 string. Just search for <a href="https://www.npmjs.com/search?q=lqip">“lqip” on npmjs.com</a> to find countless packages doing exactly that for Vite and Webpack.</p><pre>&lt;img src=&quot;big.jpg&quot; style=&quot;background:url(&#39;data:image/jpeg;base64,..&#39;)&quot;&gt;</pre><p>The drawback here, of course, is base64 makes files <strong>33% larger</strong>. So… hmm, yes, less requests but bigger payloads. ¯\_(ツ)_/¯</p><p><a href="https://blurha.sh/">Blurhash</a> attempts to combat this. Rather than embedding a whole-ass image, a base83 string representation is created of a very <em>very </em><strong><em>very </em></strong>low-res version instead. This string is then embedded into the HTML element as an attribute. Using JavaScript the base83 strings are read, decoded and an <em>m</em>×<em>n </em>mesh gradient image is generated.</p><pre>&lt;img src=&quot;img.jpg&quot; data-lqip=&quot;B5JaQT0000{b~W0e&quot;&gt;</pre><p>The drawback here being… well, I’m saving that for the next section.</p><p>A final worthy mention is <a href="https://axe312ger.github.io/sqip/">SQIP</a>. Which uses SVG as LQIPs instead of tiny raster images. What’s interesting about SVG is it’s a very versatile format. It being vector based makes the graphic responsive and sharp at any resolution. Its <a href="https://yoksel.github.io/svg-filters/">filters feature</a> allows you to add some seriously sick (realtime) effects too.</p><p>… but the SVGs you embed are still going to be hundreds of bytes large. Less — but still comparable — to base64 encoded JPEGs and WebPs.</p><p>Which brings us to…</p><h3>The benefits of a pure CSS implementation</h3><p>Going back to <a href="https://blurha.sh">Blurhash</a>, a bit of JavaScript is required on the frontend. However, let’s (mentally) go through the steps such JavaScript would have to go through.</p><p>First, all the elements in the DOM that are relevant need to be queried.</p><pre>const images = document.querySelectorAll(&#39;img[data-lqip]&#39;)</pre><p>For this to work proper you will most certainly have to wait for the DOM to finish parsing.</p><pre>addEventListener(&#39;DOMContentLoaded&#39;, () =&gt; {<br>  parseLQIPs()<br>})</pre><p>You then go through each element, read the hash, decode it and finally — somehow — render the LQIP. Blurhash does this by creating the gradients in a &lt;canvas&gt;, <a href="https://github.com/woltapp/blurhash/blob/master/TypeScript/src/decode.ts#L62"><strong>pixel by pixel</strong></a>.</p><p>It feels intuitive and gives you full control over the whole process, yes. But it’s also <em>slow</em>. You know what’s faster and more direct? CSS! Because:</p><h4>CSS is blocking</h4><p>It means the browser won’t render your page before your stylesheet(s) is fully loaded. Which is a <em>good</em> thing. It does so to prevent the dreadful <a href="https://en.wikipedia.org/wiki/Flash_of_unstyled_content">Flash of Unstyled Content</a>. The best part here is that the CSS solution works from the <strong>very first moment </strong>your page is visible. Even before the entire HTML is parsed!</p><h4>CSS lets the browser do the rendering</h4><p>As a frontend web developer you should know: nothing is faster than the browser itself. Something like CSS&#39; linear-gradient() is going to outperform drawing a gradient onto a canvas via JavaScript. Better yet, the browser will also redraw the gradient whenever the element (or screen) changes size. Meaning our gradients will look ✨pristine✨ regardless of screen resolution or element size.</p><h4>CSS is easier to implement (and less code)</h4><p>Let’s say your website or webapp loads images dynamically. Applying the LQIP is literally a single line of code:</p><pre>image.style.setProperty(&#39;--lqip&#39;, lqipValue)</pre><p>Same thing if you’re using a framework like React. There’s no need for extra components et al to handle any extra logic. Just pop that CSS variable in there and Bob’s your uncle!</p><pre>const style = {<br>  &#39;--lqip&#39;: lqipValue<br>}<br><br>return &lt;img src={src} style={style} /&gt;</pre><p>Too easy.</p><p>With all that said you should now have a better understanding of LQIPs and the benefits of a pure CSS solution. Hopefully you’re as giddy as I am because it’s time for <strong><em>s c i e n c e !</em></strong></p><h3>🧪 The code and the science!</h3><p>The goal was to make a minimalist LQIP implementation that supported at least two colors. The other goal was to have the data stored in a single CSS variable.</p><figure><img alt="Comparing Lean’s LQIP versus my original goal, a two colored linear gradient" src="https://cdn-images-1.medium.com/max/1024/1*VIpJZQpWBZ04vGktphClaw.jpeg" /><figcaption>Lean’s LQIP (2nd box) using a single hue vs my initial goal (3rd box) using 2 dominant colors | Photo by <a href="https://unsplash.com/@draufsicht?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Andreas Gücklhorn</a> on <a href="https://unsplash.com/photos/birds-eye-view-photography-of-trees-and-body-of-water-mawU2PoJWfU?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></figcaption></figure><p>The first place to look, of course, is <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/integer">&lt;integer&gt;</a>. Although undocumented, CSS integers have a safe range of -1,000,000 to 1,000,000. Which makes it <em>almost</em> a 20-bit value. Still, that’s plenty of space to store some data. The other benefit being the closer the value is to 0, the less bytes you need to write it.</p><pre>--lqip:-1000000; /* 16 bytes */<br>--lqip:0; /* 9 bytes */</pre><p>But we can do better. There’s a value type in CSS that has an even bigger range. And it’s one that’s been available since CSS variables became a thing: <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value">&lt;color&gt;</a>. More specifically, its RGBA hex form: <a href="https://www.w3.org/TR/css-color-4/#hex-notation">#RRGGBBAA</a>.</p><p>RGBA are four 8-bit values put together, resulting in a single whopping 32-bit value. That’s a lot of bits to put data in. Initially the goal was to pack two colors… but greed prevailed and I decided to pack <strong><em>three</em></strong> colors instead. The first two colors being 11-bit, the last one being 10-bit.</p><figure><img alt="Bit layout of 3 colors packed into a single 32-bit value" src="https://cdn-images-1.medium.com/max/1024/1*fMHxPhy2XuCs8iZO3ErFVw.jpeg" /></figure><p>This <strong>will </strong>limit the color precision, yes. Significantly so. With fewer bits we have lower ranges to work with. But this is the trade-off we can make. After all, the LQIP is going to be some blurry mess. Exact color precision is not of the utmost importance. Besides, the LQIP is only going to be shown temporarily until the original image is loaded in. Who’s gonna notice?</p><figure><img alt="Comparison of the color ranges between 24-bit, 11-bit and 10-bit color" src="https://cdn-images-1.medium.com/max/1024/1*ENqeHZRNKfFIucULRgGp-A.png" /><figcaption>Comparing the color precision. (Note: not the entire range)</figcaption></figure><h4>Let’s get cracking</h4><p>First, we need the colors we are gonna cram into a single RGBA. There are several ways to do this, including the pretty complicated <a href="https://en.wikipedia.org/wiki/Color_quantization">color quantization</a>. But I decided to keep things simple: resize the image to a puny 3x3 resolution and grab the colors there.</p><figure><img alt="Resizing a photo of a bowl of tomatoes to a 3x3 pixelated mess" src="https://cdn-images-1.medium.com/max/1024/1*uLfqu7aJxClwfbeq5qbIfQ.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@omeganova?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Deniz Altindas</a> on <a href="https://unsplash.com/photos/a-bowl-of-red-tomatoes-jVLahCBXaJs?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></figcaption></figure><p>I didn’t want a boring, linear gradient for the LQIP. Rather, it’d be nice if there was <em>some </em>shape and definition in there. So the LQIP will be built from a solid background color and two radial gradients placed on top, in a diagonal position. The colors will be grabbed from the top-left corner, the center and the bottom-right corner pixels of the 3x3 version.</p><figure><img alt="The layers of colors and gradients that make the final LQIP" src="https://cdn-images-1.medium.com/max/1024/1*XtPT0ILlIHGXYChKfWXc_A.jpeg" /></figure><h4>Packing</h4><p>Right, time to compress the ever loving ████ out of these ███ █████ █████ f████ing colors.</p><p>Packing the colors into a 11-bit and 10-bit integer is pretty straightforward if you’re familiar with bitwise operators.</p><pre>type RGB = { r: number, g: number, b: number }<br><br>function packColor11bit(c: RGB): number {<br>  const r = Math.round((c.r / 0xFF) * 0b1111)<br>  const g = Math.round((c.g / 0xFF) * 0b1111)<br>  const b = Math.round((c.b / 0xFF) * 0b111)<br>  return (r &lt;&lt; 7) | (g &lt;&lt; 3) | b<br>}<br><br>function packColor10bit(c: RGB): number {<br>  const r = Math.round((c.r / 0xFF) * 0b111)<br>  const g = Math.round((c.g / 0xFF) * 0b1111)<br>  const b = Math.round((c.b / 0xFF) * 0b111)<br>  return (r &lt;&lt; 7) | (g &lt;&lt; 3) | b<br>}</pre><p>With the colors packed we now combine them into a single integer and create the hex code.</p><pre>const pc0 = packColor11bit(c0)<br>const pc1 = packColor11bit(c1)<br>const pc2 = packColor10bit(c2)<br>const combined = (BigInt(pc0) &lt;&lt; 21n) | (BigInt(pc1) &lt;&lt; 10n) | BigInt(pc2)<br>const hex = &#39;#&#39; + combined.toString(16).padStart(8, &#39;0&#39;)</pre><p>We put the hex code we created as a custom CSS variable in every element that requires a LQIP. Typically you’d put these only on &lt;img&gt; elements. But nothing is stopping you from putting them anywhere else, I guess.</p><pre>&lt;img src=&quot;image.jpg&quot; style=&quot;--lqip:#abcdef01&quot;&gt;</pre><h4>Unpacking</h4><p>Now comes the fun part. Unpacking the colors from the single RGBA hex code in CSS.</p><p>CSS does <strong>not</strong> have any bitwise operators. But it <strong>does </strong>have math functions like <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/round">round()</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/mod">mod()</a>. As luck would have it, that’s enough to “emulate” bitwise operations to snatch the bits from the RGBA hex.</p><p>But how do we get the bits from a specific color channel? There’s no function to just get, like, the red value from a color.</p><p>Or is there…?</p><p>Actually there is! And it’s (exclusively) possible in the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color#using_relative_colors_with_color">relative color syntax</a>.</p><pre>--new-color: color(from var(--old-color) srgb r g b / alpha);<br>/* Alternatively */<br>--new-color: rgb(from var(--old-color) r g b / alpha);</pre><p>The relative color syntax allows us to read and perform calculations on the individual channels of the color. Arbitrarily, even; you can swap the channels if you’d like! This is perfect. Because all we need to do is read from one color to create a new color.</p><p>The red of the first color are the first 4 bits of the red in the RGBA hex. This means we have to bit shift the value 4 bits to the right, then divide by 15 (the maximum 4-bit value (2⁴-1)). The calculation looks like this:</p><pre>calc(round(down, r * 255 / pow(2,4)) / 15)</pre><p>For green we need the last 4 bits of the red channel in the RGBA hex. Here we use mod() to emulate a bitwise AND:</p><pre>calc(mod(round(down, r * 255), pow(2,4)) / 15) </pre><p>With the basic maths explained, here’s the full CSS code to unpack three colors from a single RGBA hex code:</p><pre>--lqip-c0: color(<br>  from var(--lqip)<br>  srgb<br>  calc(round(down, r * 255 / pow(2,4)) / 15)<br>  calc(mod(round(down, r * 255), pow(2,4)) / 15)<br>  calc(round(down, g * 255 / pow(2,5)) / 7)<br>  / 100%<br>);<br><br>--lqip-c1: color(<br>  from var(--lqip)<br>  srgb<br>  calc(mod(round(down, g * 255 / 2), pow(2,4)) / 15)<br>  calc(((mod(round(down, g * 255), 2) * pow(2,3)) + (round(down, b * 255 / pow(2,5)))) / 15)<br>  calc(mod(round(down, b * 255 / pow(2,2)), pow(2,3)) / 7)<br>  / 100%<br>);<br><br>--lqip-c2: color(<br>  from var(--lqip)<br>  srgb<br>  calc((((mod(round(down, b * 255), pow(2,2)) * 2)) + round(down, alpha * 255 / pow(2,7))) / 7)<br>  calc(mod(round(down, alpha * 255 / pow(2,3)), pow(2,4)) / 15)<br>  calc(mod(round(down, alpha * 255), pow(2,3)) / 7)<br>  / 100%<br>);</pre><p>Hooo boy. 😑 With that out of the way we can finally construct our LQIP.</p><p>Lean figured out a radial gradient can look more attractive by using an <a href="https://leanrada.com/notes/css-only-lqip/#:~:text=Bilinear%20interpolation%20approximation%20with%20radial%20gradients">eased interpolation</a>, rather than a linear one. (I’m paraphrasing here) So for our gradients we’ll do just that: set the steps at every 10% to simulate the easing. The background property — and the final piece of the puzzle — looks like this:</p><pre>background:<br>  radial-gradient(<br>    150% 75% at 80% 100%,<br>    var(--lqip-c2),<br>    rgb(from var(--lqip-c2) r g b / 98%) 10%,<br>    rgb(from var(--lqip-c2) r g b / 92%) 20%,<br>    rgb(from var(--lqip-c2) r g b / 82%) 30%,<br>    rgb(from var(--lqip-c2) r g b / 68%) 40%,<br>    rgb(from var(--lqip-c2) r g b / 32%) 60%,<br>    rgb(from var(--lqip-c2) r g b / 18%) 70%,<br>    rgb(from var(--lqip-c2) r g b / 8%) 80%,<br>    rgb(from var(--lqip-c2) r g b / 2%) 90%,<br>    transparent<br>  ),<br>  radial-gradient(<br>    100% 75% at 40% 50%,<br>    var(--lqip-c1),<br>    rgb(from var(--lqip-c1) r g b / 98%) 10%,<br>    rgb(from var(--lqip-c1) r g b / 92%) 20%,<br>    rgb(from var(--lqip-c1) r g b / 82%) 30%,<br>    rgb(from var(--lqip-c1) r g b / 68%) 40%,<br>    rgb(from var(--lqip-c1) r g b / 32%) 60%,<br>    rgb(from var(--lqip-c1) r g b / 18%) 70%,<br>    rgb(from var(--lqip-c1) r g b / 8%) 80%,<br>    rgb(from var(--lqip-c1) r g b / 2%) 90%,<br>    transparent<br>  ),<br>  var(--lqip-c0);</pre><h3>🏁 Done!</h3><p>And there you have it. With very little code and very easy math we have:</p><ul><li>A representation of an LQIP that is incredibly minimal and requires but a few bytes to be sent to the frontend. The frontend implementation is also minuscule. A huge bandwidth saver.</li><li>An LQIP that performs well and is responsive thanks to letting the browser do the heavy lifting.</li><li>An implementation that can be applied beyond just websites and webapps (refer to Appendix B for an example).</li><li>Defied the gods and harnessed a new unthinkable power in CSS.</li></ul><p>Don’t forget, y<a href="https://frzi.github.io/lqip-css">ou can see the LQIP in action here, as well try out your own images</a>.</p><h3>What now?</h3><p>Like most things in life, it’s not perfekt; there’s still room for improvement.</p><p>For instance: near gray colors may turn more saturated and become more colorful due to the lack of precision. One could better analyze the colors picked out of the 3x3 image before packing them into the RGBA. (Or use a different method altogether.)</p><figure><img alt="A black and white photo of a skyscraper resulting in brown and green-ish colors in the LQIP" src="https://cdn-images-1.medium.com/max/1024/1*1hjjiGXhHeKgAdKMzLYDrg.jpeg" /><figcaption>A black/white image showing brown- and greenish colors in the LQIP due to the RGB channels being rounded differently when packed to lower bit values | Photo by <a href="https://unsplash.com/@nikita_pishchugin?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Nikita Pishchugin</a> on <a href="https://unsplash.com/photos/a-high-rise-building-stands-at-potsdamer-platz-in-berlin-zYyKL6LnzSU?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></figcaption></figure><p>Another idea is to add a <em>second</em> CSS variable for three more colors to create a six color mesh gradient:</p><pre>&lt;img src=&quot;image.jpg&quot; style=&quot;--lqip0:#49bc6c88; --lqip1:#8ace6d80&quot;&gt;</pre><figure><img alt="Demonstrating a 6 color mesh gradient" src="https://cdn-images-1.medium.com/max/1024/1*qQ9Dt745WQ-s1V9iTYKTCg.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@j0rt?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Jose G. Ortega Castro</a> on <a href="https://unsplash.com/photos/red-and-yellow-light-streaks-j-stvO_SzE8">Unsplash</a></figcaption></figure><p>Thank you very much for reading. Hopefully this article inspired you just as much as Lean’s article inspired me. It feels like there’s still a huge world of CSS potential left to discover. And it always excites me when people come up with new mind-boggling, ingenious, Einstein-levels of brilliance tricks. Please share them with the world!</p><ul><li>The repository can be found on <a href="https://github.com/frzi/lqip-css">GitHub</a>.</li><li>This article is also posted on <a href="https://dev.to/frzi/image-placeholders-in-pure-css-or-defying-gods-with-math-and-color-3a5d">DEV.to</a>.</li></ul><p>Thanks again! 🤗</p><h3>Appendix A: Embedded as attribute</h3><p>In the (near?) future we should be able to utilise <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/attr#color_value">attr()</a>, allowing us to put the hex code as a separate attribute and read it as a color in CSS. This would make placing the hex code in the element easier for the backend; no longer having to jam the code awkwardly in a style=&quot;&quot;.</p><pre>&lt;img src=&quot;image.jpg&quot; data-lqip=&quot;#abcdef01&quot;&gt;</pre><pre>[data-lqip] {<br>  --lqip: attr(data-lqip type(&lt;color&gt;), white);<br>  /* ... */<br>}</pre><h3>Appendix B: Native app implementation</h3><p>Being a Swift developer as well I figured it’s worth showing how easy it is to code this LQIP implementation in a SwiftUI app. Just to prove the technique is not necessarily web (CSS) exclusive.</p><figure><img alt="SwiftUI preview of the LQIP" src="https://cdn-images-1.medium.com/max/1024/1*kxroYuyOQDd4bIDUyXTT5w.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@vhladynets?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Vicky Hladynets</a> on <a href="https://unsplash.com/photos/man-in-black-crew-neck-shirt-wearing-black-sunglasses-sDDtChahKQ4">Unsplash</a></figcaption></figure><p>The Swift code can be found in the <a href="https://gist.github.com/frzi/f13203d42a47c3d167b27135d98d6098">GitHub Gist</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=73dc6dda2529" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Less Sass, more color-mix — or: Color manipulation with pure CSS]]></title>
            <link>https://frzi.medium.com/less-sass-more-color-mix-or-color-manipulation-with-pure-css-dbf6f244ef29?source=rss-619798037cc3------2</link>
            <guid isPermaLink="false">https://medium.com/p/dbf6f244ef29</guid>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[less]]></category>
            <category><![CDATA[web-design]]></category>
            <category><![CDATA[sass]]></category>
            <category><![CDATA[css]]></category>
            <dc:creator><![CDATA[Freek]]></dc:creator>
            <pubDate>Sun, 23 Apr 2023 18:18:56 GMT</pubDate>
            <atom:updated>2025-08-16T23:05:25.688Z</atom:updated>
            <content:encoded><![CDATA[<h3>Less Sass, more color-mix — or: Color manipulation with pure CSS</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mkBO4KjpTEYuaytm7SCYYw.png" /></figure><blockquote>Read for free <a href="https://frzi.medium.com/less-sass-more-color-mix-or-color-manipulation-with-pure-css-dbf6f244ef29?source=friends_link&amp;sk=734f3fb8cb2407de1ef9ec911c6b2772">here</a>!</blockquote><p><strong>⚠ ️This article will become redundant once all browsers support the CSS Color Module 5 </strong><a href="https://developer.chrome.com/blog/css-relative-color-syntax/"><strong>relative color syntax</strong></a><strong>.</strong></p><p>color-mix() is creeping into browsers! It&#39;s now available in Safari, Chrome (partially (see bottom)) and should ship in Firefox soon (currently behind a config flag).</p><p>This article assumes you’re already aware of color-mix()&#39;s existence. If this is the first time you&#39;re reading about it, I highly recommend you read <a href="https://developer.chrome.com/blog/css-color-mix/">Chrome Developer&#39;s blogpost about this amazing new CSS function</a> first.</p><p>Just like <a href="https://developer.chrome.com/articles/css-nesting/">nesting</a>, CSS is slowly growing stronger by adopting features we normally would need preprocessors for (like Less and Sass). Another one of those features — which this article is all about — is <em>color manipulation</em>.</p><h3>Changing the alpha value (opacity)</h3><p>In both Less and Sass we can use variables to create the same color but with a different alpha value.</p><pre>$color-primary: rgb(255 125 0);<br>color: rgb($color-primary, 0.5);</pre><p>The alpha value can even be overridden if it was specifically declared in the variable.</p><pre>$color-highlight: rgb(255 125 0 / 0.5); // Alpha of 0.5<br>background-color: rgb($color-highlight, 0.9); // Overridden to have an alpha of 0.9</pre><p>Now this isn’t <em>really</em> anything special. We can already achieve something similar with pure CSS. That is, if we were to declare the RGB values in a separate variable we can use said variable in rgb():</p><pre>--color-primary-rgb: 255 125 0;<br>--color-primary: rgb(var(--color-primary-rgb));<br><br>background-color: rgb(var(--color-primary-rgb) / 0.75);<br>color: var(--color-primary);</pre><p>Of course, if we were to do this for <em>every</em> <strong><em>possible</em></strong> color, we’d end up with a hell of a lot variables.</p><p>Luckily we can also adjust the alpha value with color-mix()! The magic trick is using transparent:</p><pre>--color-primary: rgb(255 125 0);<br>color: color-mix(in srgb, transparent, var(--color-primary) 75%); /* rgb(255 125 0 / 0.75)</pre><p>By mixing a color with transparent we get the same color back but with a different alpha value. Because the 75% is grouped with the CSS variable it means the resulting color uses 75% of the color on the right, which has an alpha value of 1.0. Thus the resulting color would be the same as rgb(255 125 0 / 0.75). If the 75% was grouped with transparent then the resulting color would instead be rgb(255 125 0 / 0.25). It doesn’t matter whether transparent is used left or right. The important part is where you place the percentage.</p><blockquote>Omitting the percentage entirely would result in a color with alpha value 0.5.</blockquote><h3>darken() and lighten()</h3><p>Both Less and Sass supply a bunch of useful color functions. Two of those are darken() and lighten(). As their name already tells you these functions return a darker and a lighter variant of a given color, based on a give percentage. These are useful to create, for example, colors for hover or various states.</p><pre>@color-primary: rgb(255 125 0);<br>background-color: darken(@color-primary, 20%); // A &#39;20%&#39; darker color.</pre><p>But thanks to color-mix() we can now get the same effect using pure CSS! The trick? Use either black or white to darken or lighten the color respectively:</p><pre>--color-primary: rgb(255 125 0);<br>background-color: color-mix(in srgb, black 20%, var(--color-primary)); /* Darken by 20% */<br>background-color: color-mix(in srgb, white 20%, var(--color-primary)); /* Lighten by 20% */</pre><p>Now there are two things to keep in mind:</p><ul><li>The percentage you use in the Less/Sass functions does not match what you’d use in color-mix().</li><li>Depending on the colorspace you will get different results.</li></ul><p><a href="https://codepen.io/frzi/pen/qBJaJLM">In this Codepen we can compare and test colors between the available colorspaces as well as Sass’ darken() function</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hsQfmQX2MpM0U1LdYc-IIA.png" /><figcaption>Sass’ darken() vs color-mix red &gt; black</figcaption></figure><p>The first two columns show Sass’ darken() function. All columns demonstrate a range from 10% to 90%, with the exception of the second column: which uses 5% to 50%. This is because both darken() and lighten() are pretty &#39;sensitive&#39;.</p><p>It looks like using srgb (third column) and <em>doubling</em> the value you&#39;d normally use in Sass gives similar results.</p><p>Testing with blue supports this theory:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*r2gPfnVzA8dua1ngXNnkdg.png" /><figcaption>Sass’ darken() vs color-mix blue &gt; black</figcaption></figure><p><em>However</em>, once we try green the theory clearly fails:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mp4ODVLBGom5uD6Ar2UWJQ.png" /><figcaption>Sass’ darken() vs color-mix green &gt; black</figcaption></figure><p>So keep this in mind: switching from Less or Sass’ darken() and lighten() to color-mix() isn&#39;t a simple one-to-one translation. You&#39;re gonna have to carefully test each color to make sure you either get the same - or at least similar - result.</p><p>For completion sake, here’s comparing Sass’ lighten() with using white in color-mix():</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2WCQ56dArVFpgGehk07GDw.png" /><figcaption>Sass’ lighten() vs color-mix red &gt; white</figcaption></figure><h3>Conclusion</h3><p>So, that’s another usecase where we can use pure CSS instead of Less or Sass. Does this mean Less and Sass are becoming obsolete? Heavens no. They’re preprocessors! And preprocessors will always have a role in build pipelines. Especially for bigger projects. They help us prevent shipping certain code to production. They provide us with useful utilities to either automate or write less code (e.g. loops and mixins). They’ll be with us for a long time.</p><p>That said, both Less and Sass’ role ends after compiling the CSS. They can’t help us much during <em>runtime</em>. Which is exactly where features like CSS variables, calc(), color-mix(), et al. show their strength. And thus should take precedence over Less/Sass solutions.</p><h3>Known bugs</h3><p>As of writing this there’s a bug in Chrome (v112) where using color-mix() with CSS variables only works for color and background-color. Anything else (box-shadow, border-color, etc...) seems to give undefined behavior. Resulting in either black or the color defined in the CSS variable remaining untouched. Firefox and Safari seem fine, however.</p><p>Edit: This issue has been <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1442130">fixed</a> and should arrive in either Chrome 114 or 115!</p><p>Thanks for reading!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dbf6f244ef29" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using Combine instead of delegates — or: Passing through a different way of thinking]]></title>
            <link>https://frzi.medium.com/using-combine-instead-of-delegates-or-passing-through-a-different-way-of-thinking-ea376849db14?source=rss-619798037cc3------2</link>
            <guid isPermaLink="false">https://medium.com/p/ea376849db14</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[combine]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Freek]]></dc:creator>
            <pubDate>Wed, 21 Dec 2022 23:03:28 GMT</pubDate>
            <atom:updated>2022-12-21T23:03:28.659Z</atom:updated>
            <content:encoded><![CDATA[<h3>Using Combine to replace delegates and notifications — or: Passing through a different way of thinking</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1T3VwFsXdDiYqw8zxXDUig.jpeg" /></figure><p>The delegation pattern can be found everywhere in Apple’s frameworks. You’ve most likely used it countless times if you’ve developed iOS or macOS apps. Let’s go over it <em>real</em> quick for the sake of this article.</p><p>You have an object (the sender) that references another object (the delegate):</p><pre>class AudioPlayer {<br>    weak var delegate: AudioPlayerDelegate?<br>}</pre><p>You have a protocol for said delegate:</p><pre>protocol AudioPlayerDelegate: AnyObject {<br>    func audioPlayerConnected(_ audioPlayer: AudioPlayer)<br>    func audioPlayer(_ audioPlayer: AudioPlayer, changedSong song: Song)<br>    // Etc...<br>}</pre><p>And at some point the sender object interacts with the delegate. For example to inform a certain event has occurred:</p><pre>delegate?.audioPlayerConnected(self)</pre><p>This is all easy to understand and it has served us well for decades.</p><p>Now let’s look at a different implementation to achieve something similar.</p><p>In 2019 Apple introduced <a href="https://developer.apple.com/documentation/combine">Combine</a>. A powerful framework that allows for reactive programming with a stream-like interface. One particularly interesting object in Combine we’ll be focusing on is <a href="https://developer.apple.com/documentation/combine/passthroughsubject">PassthroughSubject</a>. It&#39;s a publisher that simply passes through a value to its subscribers when you send it one. That&#39;s it. And as it turns out it makes for a great way to define events!</p><p>Here’s what it could look like:</p><pre>class AudioPlayer {<br>    let onConnected = PassthroughSubject&lt;Void, Never&gt;<br>    let onSongChanged = PassthroughSubject&lt;Song, Never&gt;<br>}</pre><p>The sender object no longer contains a weak var delegate property. Nor is there a delegate protocol. Rather, the object exposes its events as PassthroughSubject publishers. Each defined with the type of value it passes through (using Void if there is no value), and has Never for Failure: meaning the publisher will never send an error. This makes subscribing to the publisher a whole lot easier.</p><p>Objects can subscribe to these events wherever and whenever they wish, just like you would with any other Combine publisher:</p><pre>private var cancellables = Set&lt;AnyCancellable&gt;()<br><br>audioPlayer<br>    .onSongChanged<br>    .sink { song in<br>        print(&quot;Current song is&quot;, song.title)<br>    }<br>    .assign(to: &amp;cancellables)</pre><p>When the event in question occurred the object can inform all the subscribers using send():</p><pre>onSongChanged.send(newSong)</pre><blockquote>Use a tuple to send multiple values at once:<br>let onUserRequest = PassthroughSubject&lt;(User, Song), Never&gt;()</blockquote><p>That’s the gist of it. It’s clearly a different way of thinking and programming, and if you’re unfamiliar with this pattern it may take a second to fully wrap your head around it.</p><blockquote>Notice how the names of these PassthroughSubjects start with &#39;on&#39;. This is to better differentiate the events from all other properties of the object. A clear indication this is an &#39;event&#39;.</blockquote><p>Needless to say, let’s go through the ups and downs of using this pattern.</p><h3>Unlimited subscribers</h3><p>With the delegation pattern you tend to have only <em>one</em> of them:</p><pre>weak var delegate: AudioPlayerDelegate?</pre><p>But this can be pretty limiting. Now only <em>one</em> object has the privilege of listening to events.</p><p>To combat this developers have either resorted to NotificationCenter (which is both cumbersome and <strong>not</strong> type safe!) or have come with their own custom solution:</p><pre>public var listeners = Set&lt;WeakValue&lt;AudioPlayerDelegate&gt;&gt;()<br><br>// Adding a listener:<br>audioPlayer.listeners.insert(WeakValue(self))<br><br>// `AudioPlayer` informing all listeners:<br>listeners.forEach { $0.value?.audioPlayerConnected(self) }</pre><p>Which is cute and all, but rather unstandardized and unfamiliar to anyone but themself.</p><p>Combine brings Swift users a well documented and, dare we say, standardized way of writing reactive code. No need to reinvent the wheel.</p><h3>Subscribe only to what you care about</h3><p>Delegates work with protocols:</p><pre>protocol AudioPlayerDelegate: AnyObject {<br>    func audioPlayerConnected(_ audioPlayer: AudioPlayer)<br>    func audioPlayerDisconnected(_ audioPlayer: AudioPlayer)<br>    func audioPlayer(_ audioPlayer: AudioPlayer, changedSong song: Song)<br>    func audioPlayer(_ audioPlayer: AudioPlayer, changedQuality quality: Quality)<br>    // Etc...<br>}</pre><p>The protocol defines the interface — i.e. the required methods and properties — of an object. However, sometimes we don’t care about <em>all</em> the events. We just want to listen to one event. And… well you can see where this is going. This could mean you’ll have objects containing empty methods or properties when conforming to a protocol:</p><pre>class NowPlayingUI: AudioPlayerDelegate {<br>    func audioPlayer(_ audioPlayer: AudioPlayer, changedSong song: Song) {<br>        albumArt.image = UIImage(contentsOf: song.albumArt.path)<br>        titleLabel.text = song.title<br>    }<br><br>    // We don&#39;t care about these. But Swift demands we include them.<br>    func audioPlayerConnected(_ audioPlayer: AudioPlayer) {}<br>    func audioPlayerDisconnected(_ audioPlayer: AudioPlayer) {}<br>    func audioPlayer(_ audioPlayer: AudioPlayer, changedQuality quality: Quality) {}<br>}</pre><p>One horrible way to circumvent this is to use the disgusting @obj optional keywords. But as Swift grows we should rely <em>less</em> on these nasty ugly legacy Objective-C features.</p><p>With Combine other objects are able to subscribe <em>only</em> to whatever event they care about.</p><p>With that all said and done let’s look at some of the challenges when using PassthroughSubjects for events...</p><h3>Always having to use [weak self]</h3><p>Every time you use .sink on a publisher, and you reference self in any way, you&#39;re almost always forced to use [weak self].</p><pre>audioPlayer<br>    .onSongChanged<br>    .sink { [weak self] song in<br>        self?.songTitle = song.title<br>        self?.songDuration = Double(song.duration) / 1000<br>    }<br>    .store(in: &amp;cancellables)</pre><p>Okay — so you’re not <em>forced</em> per se, but it is <em>safer</em>. This is of course to prevent circular references and to prevent keeping objects in memory beyond their lifespan. Using [weak self] can be tedious as you then have to work with an optional self. However, if you&#39;re feeling courageous you can use [unowned self]! (But seriously though, don&#39;t...)</p><h3>One-way communication</h3><p>The original delegation pattern allows for two-way communication between the sender and the delegate, if so desired. This allows the sender to ask the delegate to return or modify a value. Or to tell the sender how to respond to a certain event.</p><pre>// Asking the delegate for a value.<br>quality = delegate?.audioPlayerPreferredQuality(self) ?? .auto<br><br>// Asking the delegate whether it&#39;s okay to proceed.<br>guard delegate?.audioPlayer(self, allowStream: url) != false else {<br>    return<br>}</pre><p>Two-way communication is not Combine’s intended purpose!</p><h3>Apple-bound (-ish)</h3><p>Combine is a framework developed by Apple <em>for</em> Apple platforms. It’s closed source and only works on iOS (iPadOS), macOS, tvOS and watchOS. Which means using Combine can be a bit of a challenge if you were using Swift for Linux or Windows projects.</p><p>Luckily there are open source alternatives — like <a href="https://github.com/cx-org/CombineX">CombineX</a> and <a href="https://github.com/OpenCombine/OpenCombine">OpenCombine</a> — that strive to become a perfect replica of Apple’s Combine framework. Except open source and not bound to Apple platforms.</p><h3>Conclusion</h3><p>Combine is an extremely powerful and convenient framework. It playing a prominent role in SwiftUI means a lot of developers will become familiarized with it (many already have). Meaning there’s no need to reinvent the wheel yourself. Using PassthroughSubject for events will allow you (and perhaps other developers) to further harnass the power of Combine.</p><p>Nonetheless, as shown above, it is not necessarily a full replacement for the delegation pattern. It’s important to fully understand the flow of your code before deciding to go with PassthroughSubjects (that is, if this article has you convinced!).</p><p>For me personally however, I can say I’ve written <em>far less</em> delegate and NotificationCenter code since the introduction of Combine. 😉</p><p>Thanks for reading!</p><h3>Extra</h3><p>If PassthroughSubject&lt;T, Never&gt; is too much of a mouthful for you then consider using a typealias to make things slightly more convenient:</p><pre>typealias Event&lt;T&gt; = PassthroughSubject&lt;T, Never&gt;<br><br>let onStart = Event&lt;Void&gt;()<br>let onSongChanged = Event&lt;Song&gt;()</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ea376849db14" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A simple chat app with SwiftUI and WebSockets — or: Swift in the back, Swift in the front!]]></title>
            <link>https://frzi.medium.com/a-simple-chat-app-with-swiftui-and-websockets-or-swift-in-the-back-swift-in-the-front-78b34c3dc912?source=rss-619798037cc3------2</link>
            <guid isPermaLink="false">https://medium.com/p/78b34c3dc912</guid>
            <category><![CDATA[websocket]]></category>
            <category><![CDATA[tutorial]]></category>
            <category><![CDATA[vapor]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[swiftui]]></category>
            <dc:creator><![CDATA[Freek]]></dc:creator>
            <pubDate>Sun, 23 Aug 2020 20:00:57 GMT</pubDate>
            <atom:updated>2021-07-31T15:43:30.531Z</atom:updated>
            <content:encoded><![CDATA[<h3>A simple chat app with SwiftUI and WebSockets — or: Swift in the back, Swift in the front!</h3><blockquote>Creating a very primitive chat app in SwiftUI, while using Swift and WebSockets to create the chat server. It’s Swift from top to bottom, honey!</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5dtw2zZVr57Of6rAq7RHgA.jpeg" /></figure><p>This tutorial is also available in <em>Markdown</em>, together with the final code, on <a href="https://github.com/frzi/SwiftChatApp">Github</a>.</p><figure><img alt="The app we’re building together." src="https://cdn-images-1.medium.com/max/1024/1*5CHlJ6L0Sfepsc6fBfIH_A.png" /></figure><h3>Introduction</h3><p>In this tutorial we’ll make a rather primitive, but functional chat app. The app will run on iOS or macOS — or both! The beauty of SwiftUI is how little effort it takes to make a multiplatform app.</p><p>Of course, a chat app will have very little use without a server to talk to. Hence we’ll be making a very primitive chat server as well, utilizing WebSockets. Everything will be built in Swift and run locally on your machine.</p><p>This tutorial assumes you already have a bit of experience developing iOS/macOS apps using SwiftUI. Although concepts will be explained as we go, not everything will be covered in depth. Needless to say, if you type along and follow the steps, by the end of this tutorial you’ll have a working chat app (for iOS and/or macOS), that communicates with a server that you also made! You will also have a basic understanding of concepts like server-side Swift and WebSockets.</p><p>If none of that interests you, you can always just download the <a href="https://github.com/frzi/SwiftChatApp">final code from Github</a>.</p><h4>Quick summary of what’s to come</h4><p>In short, we will start by making a very simple, plain, featureless server. We’ll build the server as a Swift Package, then add the <a href="https://github.com/vapor/vapor/">Vapor web framework</a> as a dependency. This will help us setup a WebSocket server with just a few lines of code.</p><p>Afterwards we will start building the frontend chat app. Quickly starting with the basics, then adding features (and necessities) one by one.</p><p>Most of our time will be spent working on the app, but we’ll be going back and forth between the server code and the app code as we add new features.</p><h4>Requirements</h4><ul><li>macOS 10.15+</li><li>Xcode 12 beta 5+</li></ul><p><em>Optional</em></p><ul><li>macOS 11 beta<br>(if you want to run the app on macOS)</li><li>iPhone/iPad running iOS 14 beta 5+<br>(if you want to run the app on a physical device)</li></ul><p><strong>Let’s begin!</strong></p><h3>Creating the server</h3><p>Open Xcode 12 and start a new project (<em>File &gt; New Project</em>). Under <em>Multiplatform</em> select <em>Swift Package</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GeUgJO8UYBtuM5EceDvpAg.png" /></figure><p>Call the Package something logical — something self explanatory — like “<em>ChatServer</em>”. Then save it wherever you like.</p><blockquote><strong>Swift Package?</strong></blockquote><blockquote>When creating a framework or multiplatform software (e.g. for macOS and Linux) in Swift, Swift Packages are the preferred way to go. They’re the official solution for creating modular code that other Swift projects can easily use. A Swift Package doesn’t necessarily have to be a modular project though: it can also be a stand-alone executable that simply uses other Swift Packages as dependencies (which is what we’re doing).</blockquote><blockquote>It may have occurred to you that there’s no Xcode project (.xcodeproj) present for the Swift Package. To open a Swift Package in Xcode like any other project, simply open the Package.swift file. Xcode should recognize you&#39;re opening a Swift Package and opens the entire project structure. It will automatically fetch all the dependencies at the start.</blockquote><blockquote>You can read more about Swift Packages and Swift Package Manager on the <a href="https://swift.org/package-manager/">official Swift website</a>.</blockquote><h4>Setup Package.swift</h4><p>To handle all the heavy lifting of setting up a server, we’ll be using the <a href="https://github.com/vapor/vapor/">Vapor web framework</a>. Vapor comes with all the necessary features to create a WebSocket server.</p><blockquote><strong>WebSockets?</strong></blockquote><blockquote>To provide the web with the ability to communicate with a server in realtime, WebSockets were created. It’s a well described spec for safe realtime (low-bandwidth) communication between a client and a server. E.g.: multiplayer games and chat apps. <a href="https://agar.io/">Those</a> <a href="https://krunker.io/">addictive</a> <a href="http://slither.io/">in-browser</a> <a href="https://gameofbombs.com/">multiplayer</a> <a href="https://pie.ai/">games</a> you’ve been playing on valuable company time? Yup, WebSockets!</blockquote><blockquote>However, if you wish to do something like realtime video streaming you’re best looking for a different solution. <em>🙂</em></blockquote><blockquote>Though we’re making an iOS/macOS chat app in this tutorial, the server we’re making can just as easily talk to other platforms with WebSockets. Indeed: if you want you could also make an Android and web version of this chat app, talking to the same server and allowing for communication between all platforms!</blockquote><blockquote><strong>Vapor</strong>?</blockquote><blockquote>The internet is a complex series of tubes. Even responding to a simple HTTP request requires some serious amount of code. Luckily, experts in the field have developed open source web frameworks that do all the hard work for us for decades now, in various programming languages. <a href="https://github.com/vapor/vapor/">Vapor</a> is one of them, and it’s written in Swift. It already comes with some WebSocket capabilities and it’s exactly what we need.</blockquote><blockquote>Vapor isn’t the only Swift powered web framework though. <a href="https://github.com/IBM-Swift/Kitura">Kitura</a> and <a href="https://github.com/PerfectlySoft/Perfect">Perfect</a> are also well known frameworks. Though Vapor is arguably more active in its development.</blockquote><p>Xcode should open the Package.swift file by default. This is where we put general information and requirements of our Swift Package.</p><p>Before we do that though, look in the Sources/ChatServer folder. It should have a ChatServer.swift file. We need to rename this to main.swift. Once that&#39;s done, return to Package.swift.</p><p>Under products:, remove the following value:</p><pre>.library(name: &quot;ChatServer&quot;, targets: [&quot;ChatServer&quot;])</pre><p>… and replace it with:</p><pre>.executable(name: &quot;ChatServer&quot;, targets: [&quot;ChatServer&quot;])</pre><p>After all, our server isn’t a Library. But a stand-alone executable, rather. We should also define the platforms (and minimum version) we expect our server to run on. This can be done by adding platforms: [.macOS(v10_15)] under name: &quot;ChatServer&quot;:</p><pre>name: &quot;ChatServer&quot;,<br>	platforms: [<br>		.macOS(.v10_15),<br>	],</pre><p>All this should make our Swift Package ‘runnable’ in Xcode.</p><p>Alright, let’s add Vapor as a dependency. In dependencies: [] (which should have some commented-out stuff), add the following:</p><pre>.package(url: &quot;https://github.com/vapor/vapor.git&quot;, from: &quot;4.0.0&quot;)</pre><p>When saving the Package.swift file, Xcode should start automatically fetching the Vapor dependencies with verison 4.0.0 or newer. As well as all <em>its</em> dependencies.</p><p>We just have to make one more adjustment to the file while Xcode is doing its thing: adding the dependency to our target. In targets: you will find a .target(name: &quot;ChatServer&quot;, dependencies: []). In that empty array, add the following:</p><pre>.product(name: &quot;Vapor&quot;, package: &quot;vapor&quot;)</pre><p><em>That’s it</em>. Our Package.swift is done. We&#39;ve described our Swift Package by telling it:</p><ul><li>It’s an executable, not a library</li><li>To import the Vapor web framework dependency (and all <em>its </em>dependencies)</li><li>Link the Vapor dependency to our executable, making it accessible in our code</li></ul><p>The final Package.swift should look like this(-ish):</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/501dc7f24ab68b420978bc6330c264c6/href">https://medium.com/media/501dc7f24ab68b420978bc6330c264c6/href</a></iframe><p>Now, it’s finally time for…</p><h3>Writing some actual code</h3><p>In Xcode, open Sources/ChatServer/main.swift and delete everything in there. It&#39;s worthless to us. Instead, make main.swift look like this:</p><pre>import Vapor<br><br>var env = try Environment.detect() // 1<br>let app = Application(env) // 2<br><br>defer { // 3<br>	app.shutdown()<br>}<br><br>app.webSocket(&quot;chat&quot;) { req, client in // 4<br>	print(&quot;Connected:&quot;, client)<br>}<br><br>try app.run() // 5</pre><p>💥 Bam! That’s all it takes to start a (WebSocket) server using Vapor. Look at how effortless that was.</p><ol><li>First we make a default Environment configuration.</li><li>We initialize a Vapor Application instance and pass it the Environment.</li><li>Register a defer and call .shutdown() which will perform any cleanup when exiting the program.</li><li>Start listening to any incoming WebSocket connections on /chat.</li><li>Acually start the Vapor Application instance.</li></ol><p>Now ▶️ run the program in Xcode and grab something to drink. Building the first time takes a while as Xcode will need to build all those Vapor dependencies first. (But only once)</p><p>Once the program has successfully run, you may not see anything resembling an app. That’s because server software don’t tend to have graphical user interfaces. But rest assured, the program is alive and well in the background, spinning its wheels. The Xcode console should show the following message, however:</p><pre>notice codes.vapor.application : Server starting on http://127.0.0.1:8080</pre><p>This means the server can successfully listen to incoming requests. This is great, because we now have a WebSocket server we can start connecting to!</p><blockquote><strong>I don’t believe you?</strong></blockquote><blockquote>If for whatever reason you think I’ve been spewing nothing but heinous lies this whole time, you can test the server yourself!</blockquote><blockquote>Open up your favourite browser and make sure you’re in an empty tab. (If it’s Safari, you will need to enable <a href="https://support.apple.com/guide/safari/sfri20948/mac">Developer mode</a> first.) Open the <em>Inspector</em> (Cmd+Option+I) and go to the <em>Console</em>. Type in</blockquote><blockquote>new WebSocket(&#39;ws://localhost:8080/chat&#39;)</blockquote><blockquote>and hit Return. Now take a look at the Xcode console. If all went well, it should now show Connected: WebSocketKit.WebSocket.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Z4Ip7UZRmWFj8QMjcNhobA.png" /><figcaption>Testing the WebSocket connection in a browser.</figcaption></figure><h4>⚠️ Important ⚠️</h4><p><em>The server is only accessible from your local machine. This means you cannot connect your physical iPhone/iPad to the server. Instead, we’ll be using the Simulator in the following steps to test our chat app.</em></p><p><em>To test the chat app on a physical device, some (small) extra steps need to be taken. Refer to Appendix A for more details.</em></p><h3>Creating the app</h3><p>Though we’re not done with the backend yet, it’s time to move to the frontend. The chat app itself!</p><p>In Xcode create a new project. This time, under <em>Multiplatform</em> select <em>App</em>. Again, choose a beautiful name for your app and continue. (I chose <em>SwiftChat</em>. I agree, it’s <em>perfect</em> 🌈)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f6eTTMczwQx5Ts90Q-XPxA.png" /></figure><p>The app does not rely on any external third-party frameworks or libraries. Indeed, everything we need is available via Foundation, Combine and SwiftUI (in Xcode 12+).</p><p>Let’s start working on the chat screen immediately. Create a new Swift file and name it ChatScreen.swift. It doesn&#39;t matter whether you choose the <em>Swift File</em> or the <em>SwiftUI View</em> template. We&#39;re deleting everything in it regardless.</p><p>Here’s the starter’s kit of ChatScreen.swift:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/639d04db07b3a633d3a0cbe57d5cf8f0/href">https://medium.com/media/639d04db07b3a633d3a0cbe57d5cf8f0/href</a></iframe><p>In ContentsView.swift, replace the <em>Hello World</em> with ChatScreen():</p><pre>struct ContentView: View {<br>	var body: some View {<br>		ChatScreen()<br>	}<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cf3NDo8RY-6BECZcBXAtxQ.png" /><figcaption>A blank canvas, for now.<br>Left: iPhone with dark appearance. Right: iPad with light appearance.</figcaption></figure><p>What we have here:</p><ol><li>A ScrollView where we will place all our messages in.</li><li>The message box where the user can type in their message.</li><li>A submit button to send the message (though later on users will also be able to submit by pressing Return).</li><li>Disabling the submit button if the message is empty.</li></ol><p>If you wish to make different design choices, go right ahead. 🙂</p><h4>Connecting to the server</h4><p>Now let’s start working on some non-UI related logic: connecting to the very server we just made.</p><p><em>SwiftUI</em>, together with the <em>Combine</em> framework, provides developers with tools to implement <a href="https://en.wikipedia.org/wiki/Separation_of_concerns">Seperation of Concerns</a> effortlessly in their code. Using the ObservableObject protocol and @StateObject (or @ObservedObject) property wrappers we can implement non-UI logic (referred to as <a href="https://en.wikipedia.org/wiki/Business_logic"><em>Business Logic</em></a>) in a separate place. As things should be! After all, the only thing the UI should care about is displaying data to the user and reacting to user input. It shouldn&#39;t care where the data comes from, or how it&#39;s manipulated.</p><p>Coming from a <a href="https://reactjs.org/">React</a> background, this luxury is something I’m incredibly envious of.</p><blockquote>There are thousands upon thousands articles and discussions about software architecture. You’ve probably heard or read about concepts like MVC, MVVM, VAPOR, Clean Architecture and more. They all have their arguments and their applications.</blockquote><blockquote>Discussing these is out-of-scope for this tutorial. But it’s generally agreed upon that business logic and UI logic should not be intertwined.</blockquote><p>This concept is true just as much for our <em>ChatScreen</em>. The only thing the <em>ChatScreen</em> should care about is displaying the messages and handling the user-input text. It doesn’t care about ✌️WeBsOcKeTs✌, nor should it.</p><p>You can create a new Swift file or write the following code at the bottom of ChatScreen.swift. Your choice. Wherever it lives, make sure you don&#39;t forget the imports!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0b516f543c2be68706e30aa51ee10eb7/href">https://medium.com/media/0b516f543c2be68706e30aa51ee10eb7/href</a></iframe><p>This may be a lot to take in, so let’s slowly go through it:</p><ol><li>We store a URLSessionWebSocketTask in a property.<br>URLSessionWebSocketTask objects are responsible for WebSocket connections. They&#39;re residents of the URLSession family in the <em>Foundation</em> framework.</li><li>Public method to start the connection.</li><li>URL to our server.<br>Remember: the server runs locally on your machine (which means we use the IP 127.0.0.1 or localhost). The default port of Vapor applications is 8080. And we put a listener to WebSocket connections in the /chat path.</li><li>We create a URLSessionWebSocketTask and store it in the instance&#39;s propety.</li><li>We bind an ‘on message’ handler.<br>Whenever a message is received from the server, the method onReceive(incoming:) will be called. More on this later.</li><li>Start the WebSocket connection.</li><li>A public method to close the connection (very important!).</li><li>Disconnecting the WebSocket connection.<br>With WebSockets, you have the option to inform the server <em>why</em> a client disconnected. With optional additional data provided.</li><li>Make sure we gracefully disconnect when the ChatScreenModel is purged from memory.</li></ol><p>This is a great start. We now have a place where we can put all our WebSocket logic without cluttering the UI code. It’s time to have ChatScreen communicate with ChatScreenModel.</p><p>Add the ChatScreenModel as a State Object in ChatScreen:</p><pre>struct ChatScreen: View {<br>	@StateObject private var model = ChatScreenModel() // &lt;this<br>	@State private var message = &quot;&quot;<br>	<br>	// etc...<br>}</pre><p>When should we connect to the server? Well, when the screen is <em>actually</em> visible, of course. You may be tempted to call .connect() in the init() of ChatScreen. This is a dangerous thing. In fact, in SwiftUI one should try to avoid putting anything the init(), as the View can be initialized even when it will never appear. (For instance in LazyVStack or in NavigationLink(destination:).) It&#39;d be a shame to waste precious CPU cycles. Therefore, let&#39;s defer everything to onAppear.</p><p>Add an onAppear method to ChatScreen. Then add and pass that method to the .onAppear(perform:) modifier of VStack:</p><pre>struct ChatScreen: View {<br>	// ...<br><br>	private func onAppear() {<br>		model.connect()<br>	}<br><br>	var body: some View {<br>		VStack {<br>			// ...<br>		}<br>		.onAppear(perform: onAppear)<br>	}<br>}</pre><blockquote><strong>Wasted space?</strong></blockquote><blockquote>Plenty of people prefer to write the contents of these methods inline instead:</blockquote><blockquote>.onAppear { model.connect() }</blockquote><blockquote>This is nothing but a personal preference. Personally I like to define these methods separately. Yes, it costs more space. But they’re easier to find, are reusable, prevent the body from getting (more) cluttered and are arguably easier to fold. <em>🙂</em></blockquote><p>By the same token, we should also disconnect when the view disappears. The implementation should be self explanatory, but just in case:</p><pre>struct ChatScreen: View {<br>	// ...<br><br>	private func onDisappear() {<br>		model.disconnect()<br>	}<br><br>	var body: some View {<br>		VStack {<br>			// ...<br>		}<br>		.onAppear(perform: onAppear)<br>		.onDisappear(perform: onDisappear)<br>	}<br>}</pre><p>It’s very important to close WebSocket connections whenever we stop caring about them. When you (gracefully) close a WebSocket connection, the server will be informed and can purge the connection from memory. The server should <em>never</em> have dead or unknown connections lingering in memory.</p><p>Phew. Quite a ride we’ve been through so far. Time to test it out. ▶️ Run the app! (Use Simulator if you’re testing for iOS.) Make sure you still have the server running in your other Xcode window. When the app has successfully started and is displaying ChatScreen, you should see the Connected: WebSocketKit.WebSocket message in the Xcode console of the server. If not, retrace your steps and start debugging!</p><h4>Testing disconnection</h4><p>One more thing™️. We should also test whether the WebSocket connection is closed when the user closes the app (or leaves ChatScreen). Head back to the main.swift file of the server project. Currently our WebSocket listener looks like this:</p><pre>app.webSocket(&quot;chat&quot;) { req, client in<br>	print(&quot;Connected:&quot;, client)<br>}</pre><p>Add a handler to the .onClose of client, performing nothing but a simple print():</p><pre>app.webSocket(&quot;chat&quot;) { req, client in<br>	print(&quot;Connected:&quot;, client)</pre><pre>	client.onClose.whenComplete { _ in<br>		print(&quot;Disconnected:&quot;, client)<br>	}<br>}</pre><p>Re-run the server and start the chat app. Once the app is connected, close the app (actually exit it, don’t just put it in the background). The Xcode console of the server should now print Disconnected: WebSocketKit.WebSocket. This confirms that WebSocket connections are indeed closed when we no longer care about them. Thus the server should have no dead connections lingering in memory.</p><h3>Sending and receiving messages</h3><p>You ready to actually send something to the server? Boy, I sure am. But just for a moment, let’s put on the brakes and think for a second. Lean back in the chair and stare aimlessly, yet somehow purposefully at the ceiling…</p><p><em>What</em> exactly will be we sending to the server? And, just as importantly, <em>what</em> will we be receiving back from the server?</p><p>Your first thought may be “Well, just text, right?”, you’d be half right. But what about the time of the message? What about the sender’s name? What about an identifier to make the message unique from any other message? We don’t have anything for the user to create a username or anything just yet. So let’s put that to the side and just focus on sending and receiving messages.</p><p>We’re going to have to make some adjustments on both the app- and server-side. Let’s start with the server.</p><h4>Server-side</h4><p>Create a new Swift file in Sources/ChatServer called Models.swift in the server project. Paste (or type) the following code into Models.swift:</p><pre>import Foundation</pre><pre>struct SubmittedChatMessage: Decodable { // 1<br>	let message: String<br>}</pre><pre>struct ReceivingChatMessage: Encodable, Identifiable { // 2<br>	let date = Date() // 3<br>	let id = UUID() // 4<br>	let message: String // 5<br>}</pre><p>Here’s what’s going on:</p><ol><li>This will be the data the server receives from individual clients when they send a message. For now, it’s just a message (String). Username et al will be added later on. Because we’re only receiving this type of data, we only need to decode it. Hence the Decodable protocol.</li><li>This will be the data sent to indiviual clients. Because we only have to encode it, it’s conforming to the Encodable protocol.</li><li>The date of the message. This will be automatically generated when initializing a ReceivingChatMessage.</li><li>A unique identifier for the message. Just like the date, this too will be automatically generated.</li><li>The message received earlier, now being sent to all the clients connected to the server.</li></ol><p>Do note how we’re generating the date and id on the server-side. This makes the server the <a href="https://en.wikipedia.org/wiki/Single_source_of_truth">Source of Truth</a>. The server knows what time it is. If the date were to be generated on the client-side, it cannot be trusted. What if the client has their clock setup to be in the future? Having the server generate the date makes its clock the <em>only</em> reference to time.</p><blockquote><strong>Timezones?</strong></blockquote><blockquote>Swift’s <a href="https://developer.apple.com/documentation/foundation/date">Date</a> object always has <a href="https://developer.apple.com/documentation/foundation/nsdate">00:00:00 UTC 01-01-2001</a> as absolute reference time. When initializing a Date or format one to string (e.g. via DateFormatter), the client&#39;s locality will be taken into consideration automatically. Adding or subtracting hours depending on the client&#39;s timezone.</blockquote><blockquote><strong>UUID?</strong></blockquote><blockquote><a href="https://en.wikipedia.org/wiki/Universally_unique_identifier">Universally Unique Identifiers</a> are globally regarded as acceptable values for identifiers.</blockquote><p>We also don’t want the client to send multiple messages with the same unique identifier. Whether accidentally or purposefully maliciously. Having the server generate this identifier is one extra layer of security and less possible sources of errors.</p><p>Now then. When the server receives a message from a client, it should pass it along to every other client. This does, however, mean we have to keep track of every client that’s connected.</p><p>Back to main.swift of the server project. Right above app.webSocket(&quot;chat&quot;) put the following declaration:</p><pre>var clientConnections = Set&lt;WebSocket&gt;()</pre><p>This is where we’ll store our client connections.</p><p>But wait… You <em>should</em> be getting a big, bad, nasty compile error. That’s because the WebSocket object does not conform to the Hashable protocol by default. No worries though, this can be easily (albeit cheapishly) implemented. Add the following code at the very bottom of main.swift:</p><pre>extension WebSocket: Hashable {<br>	public static func == (lhs: WebSocket, rhs: WebSocket) -&gt; Bool {<br>		ObjectIdentifier(lhs) == ObjectIdentifier(rhs)<br>	}<br>	<br>	public func hash(into hasher: inout Hasher) {<br>		hasher.combine(ObjectIdentifier(self))<br>	}<br>}</pre><p>Badabing badaboom. The above code is a quick but simple way to make a class conform to Hashable (and by definition also Equatable), by simply using its memory address as a unique property. Note: this only works for classes. Structs will require a little more hands-on implementation.</p><p>Alright, so now that we’re able to keep track of clients, replace everything of app.webSocket(&quot;chat&quot;) (including its closure <em>and</em> its contents) with the following code 👇:</p><pre>app.webSocket(&quot;chat&quot;) { req, client in<br>	clientConnections.insert(client)<br>	<br>	client.onClose.whenComplete { _ in<br>		clientConnections.remove(client)<br>	}<br>}</pre><p>When a client connects, store said client into clientConnections. When the client disconnects, remove it from the same Set. Ezpz.</p><p>The final step in this chapter is adding the <em>heart</em> of the server↔️app communication. Below the entirety of client.onClose.whenComplete - but still inside the app.webSocket(&quot;chat&quot;) closure - add the following snippet of code:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/104993690b1ed226edc0c6787cf7e88b/href">https://medium.com/media/104993690b1ed226edc0c6787cf7e88b/href</a></iframe><p>Again, from the top:</p><ol><li>Bind an .onText handler to the connected client.<br>Everytime the server receives text from this client, this handler will be called. Here we have the opportunity to parse and validate the incoming text.</li><li>Decode the incoming message.<br>This will be our validation step. We don’t want to continue if the client sends unacceptable payloads.</li><li>Initialize a ReceivingChatMessage with the message received from the client.<br>Remember that the date and unique identifier of ReceivingChatMessage will be generated automatically.</li><li>Encode the ReceivingChatMessage to a JSON string (well, as Data).</li><li>Send the encoded JSON string to every client.<br>Yes, this includes sending it to the original sender as well.</li></ol><blockquote><strong>Why send it back?</strong></blockquote><blockquote>We can use this as a confirmation that the message was, in fact, received successfully from the client. The app will receive back the message just like it’d receive any other message. This will prevent us from having to write additional code later on.</blockquote><p>Done! The server is ready to receive messages and pass them along to other connected clients. Run the server and let it idle in the background, as we continue with the app!</p><h4>Sending client-side</h4><p>Rememeber those SubmittedChatMessage and ReceivingChatMessage structs we made for the server? We need them for the app as well. Create a new Swift file and name it Models.swift. Though you could just copy-paste the implementations, they will require a bit of modification:</p><pre>import Foundation<br><br>struct SubmittedChatMessage: Encodable {<br>	let message: String<br>}<br><br>struct ReceivingChatMessage: Decodable, Identifiable {<br>	let date: Date<br>	let id: UUID<br>	let message: String<br>}</pre><p>Notice how the Encodable and Decodable protocols have been swapped. It only makes sense: in the app, we only encode SubmittedChatMessage and only decode ReceivingChatMessage. The opposite of the server. We also removed the automatic initializations of date and id. The app has no business generating these.</p><p>Okay, back to ChatScreenModel (whether it&#39;s in a separate file or at the bottom of ChatScreen.swift). Add the top, but inside ChatScreenModel add the following instance property:</p><pre>@Published private(set) var messages: [ReceivingChatMessage] = []</pre><p>This where we’ll store received messages. Thanks to @Published, the ChatScreen will know exactly when this array gets updated and will react to this change. private(set) makes sure only ChatScreenModel can update this property. (After all, it&#39;s the owner of the data. No other object has any business modifying it directly!)</p><p>Still inside ChatScreenModel, add the following method:</p><pre>func send(text: String) {	<br>	let message = SubmittedChatMessage(message: text) // 1<br>	guard let json = try? JSONEncoder().encode(message), // 2<br>		let jsonString = String(data: json, encoding: .utf8)<br>	else {<br>		return<br>	}<br>	<br>	webSocketTask?.send(.string(jsonString)) { error in // 3<br>		if let error = error {<br>			print(&quot;Error sending message&quot;, error) // 4<br>		}<br>	}<br>}</pre><p>It seems self-explanatory. But for consistency’s sake:</p><ol><li>Create the payload we’ll be sending to the server:<br>A SubmittedChatMessage that, for now, just holds the message.</li><li>Turn our payload into a JSON string.</li><li>Send the JSON string to the server.</li><li>If any errors occurred, simply print the error.<br>Of course, in a real app you’d respond a bit more respectfully to such an error. 🙃</li></ol><p>Open ChatScreen.swift and add the following method to ChatScreen:</p><pre>private func onCommit() {<br>	if !message.isEmpty {<br>		model.send(text: message)<br>		message = &quot;&quot;<br>	}<br>}</pre><p>This method will be called when the user either presses the submit button or when pressing Return on the keyboard. Though it’ll only send the message if it <em>actually contains anything</em>.</p><p>In the .body of ChatScreen, locate the TextField and Button, then replace them (but not their modifiers or contents) with the following initializations:</p><pre>TextField(&quot;Message&quot;, text: $message, onEditingChanged: { _ in }, onCommit: onCommit)<br>	// .modifiers here</pre><pre>Button(action: onCommit) {<br>	// Image etc<br>}<br>// .modifiers here</pre><p>When the Return key is pressed while the TextField is focused, onCommit will be called. Same goes for when the Button is pressed by the user. TextField also requires an onEditingChanged argument - but we discard that by giving it an empty closure.</p><p>Now is the time to start testing what we have. Make sure the server is still running in the background. Place some breakpoints in the client.onText closure (where the server reads incoming messages) in main.swift of the server. Run the app and send a message. The breakpoint(s) in main.swift <em>should</em> be hit upon receiving a message from the app. If it did, 🎊 <em>lush</em>! 🎊 If not, well... retrace your steps and start debugging!</p><h4>Receiving client-side</h4><p>Sending messages is cute and all. But what about receiving them? (Well, technically we are receiving them, just never reacting to them.) Right you are!</p><p>Let’s visit ChatScreenModel once more. Remember that onReceive(incoming:) method? Replace it and give it a sibling method as shown below:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e49bc8b178c94432fb57af3c54f086ae/href">https://medium.com/media/e49bc8b178c94432fb57af3c54f086ae/href</a></iframe><p>So…</p><ol><li>Those receive handlers we bind to URLSessionWebSocketTask? They only work once. Thus, we instantly rebind a new handler, so we&#39;re ready to read the next incoming message.</li><li>If successful, we pick out the contents of the message and let another method deal with it further.</li><li>If not successful, simply print an error to the console.</li><li>This method is responsible for parsing the incoming (successful) message.</li><li>A WebSocket message can be either binary or text. So far we’ve been sending JSONs back and forther — which <em>are</em> text formats (more on this later). Thus we only handle messages containing strings. Afterwards we decode the data to ReceivingChatMessage.</li><li>Plop the decoded message into self.messages. <em>However</em>, because URLSessionWebSocketTask can call the receive handler on a different thread, and because SwiftUI only works on the main thread, we have to wrap our modification in a DispatchQueue.main.async {}, assuring we&#39;re actually performing the modification on the main thread.</li></ol><blockquote>Explaining the hows and whys of working with different threads in SwiftUI is beyond the scope of this tutorial.</blockquote><p>Nearly there!</p><p>Check back in on ChatScreen.swift. See that empty ScrollView? We can <em>finally</em> populate it with messages:</p><pre>ScrollView {<br>	LazyVStack(spacing: 8) {<br>		ForEach(model.messages) { message in<br>			Text(message.message)<br>		}<br>	}<br>}</pre><p>It’s not going to look spectacular by any means. But this’ll do the job for now. We simply represent every message with a plain o’ Text.</p><p>Go ahead, run the app. When you send a message, it should instantly appear on the screen. This confirms the message was successfully sent to the server, and the server successfully sent it back to the app! Now, if you can, open up multiple instances of the app (tip: use different Simulators). There’s virtually no limit to the amount of clients! Have a nice big chat party all by yourself.</p><p>Keep sending messages until there’s no room left on the screen. Notice anything? Yarp. The ScrollView doesn&#39;t automatically scroll to the bottom once new messages are beyond the screen&#39;s bounds. 😟</p><p>Enter…</p><h4>Autoscrolling</h4><p>Remember, the server generates a unique identifier for each message. We can finally put it to good use! The wait was worth it for this incredible payoff, I assure you.</p><p>In ChatScreen, turn the ScrollView into this beauty:</p><pre>ScrollView {<br>	ScrollViewReader { proxy in // 1<br>		LazyVStack(spacing: 8) {<br>			ForEach(model.messages) { message in<br>				Text(message.message)<br>					.id(message.id) // 2<br>			}<br>		}<br>		.onChange(of: model.messages.count) { _ in // 3<br>			scrollToLastMessage(proxy: proxy)<br>		}<br>	}<br>}</pre><p>Then add the following method:</p><pre>private func scrollToLastMessage(proxy: ScrollViewProxy) {<br>	if let lastMessage = model.messages.last { // 4<br>		withAnimation(.easeOut(duration: 0.4)) {<br>			proxy.scrollTo(lastMessage.id, anchor: .bottom) // 5<br>		}<br>	}<br>}</pre><ol><li>We’re wrapping the contents of the ScrollView in a ScrollViewReader.<br>The ScrollViewReader provides us with a proxy that we&#39;ll need very soon.</li><li>Give each message a unique identifier (simply using the message’s identifier).</li><li>Keep track of changes to model.messages.count. When this value changes, we call the method we just added, passing it the proxy provided by ScrollViewReader.</li><li>Safely get the latest message.</li><li>Call the .scrollTo(_:anchor:) method of the ScrollViewProxy. This tells the ScrollView to scroll to the View with the given identifier. We wrap this in withAnimation {} to animate the scrolling.</li></ol><p><em>Et voilà…</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*gsm28cOZIMy8GkUaCfYvzA.gif" /></figure><h3>Adding user information</h3><p>These messages are pretty lush… but it’d be even <em>lush-er</em> if we knew who sent the messages and visually distinguish between received and sent messages.</p><p>With each message we will also attach a username and a user identifier. Because a username isn’t enough to identify a user, we need something unique. What if the user and everyone else’s name was Patrick? We’d have an identity crisis and would be unable to distinguish between messages sent by Patrick and messages received by <em>a</em> Patrick.</p><p>As is tradition, we start with the server, it’s the least amount of work.</p><p>Open up Models.swift where we defined both SubmittedChatMessage and ReceivingChatMessage. Give both of these bad boys a user: String and userID: UUID property, like so:</p><pre>struct SubmittedChatMessage: Decodable {<br>	let message: String<br>	let user: String // &lt;- We<br>	let userID: UUID // &lt;- are<br>}</pre><pre>struct ReceivingChatMessage: Encodable, Identifiable {<br>	let date = Date()<br>	let id = UUID()<br>	let message: String<br>	let user: String // &lt;- new<br>	let userID: UUID // &lt;- here<br>}</pre><p><em>(Don’t forget to update the </em><em>Models.swift file in the app’s project as well!)</em></p><p>Returning to main.swift, where you should be greeted with an error, change the initialization of ReceivingChatMessage to the following:</p><pre>let outgoingMessage = ReceivingChatMessage(<br>	message: incomingMessage.message,<br>	user: incomingMessage.user,<br>	userID: incomingMessage.userID<br>)</pre><p>And <em>that&#39;s it</em>! We&#39;re done with the server. It&#39;s just the app from here on out. The home stretch!</p><p>In the app&#39;s Xcode project, create a new Swift file called UserInfo.swift. Place the following code there:</p><pre>import Combine<br>import Foundation<br><br>class UserInfo: ObservableObject {<br>	let userID = UUID()<br>	@Published var username = &quot;&quot;<br>}</pre><p>This will be our EnvironmentObject where we can store our username in. As always, the unique identifier is an automatically generated immutable UUID. Where does the username come from? The user will input this when opening the app, before being presented the chat screen.</p><p>New file time: SettingsScreen.swift. This file will house the simple settings form:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/57d956eecfb4f49fdac18fde29638e53/href">https://medium.com/media/57d956eecfb4f49fdac18fde29638e53/href</a></iframe><ol><li>The previously created UserInfo class will be accessible here as an EnvironmentObject.</li><li>A simple validation to make sure the username isn’t just whitespace.</li><li>The TextField will directly write its contents into userInfo.username.</li><li>The NavigationLink that will present ChatScreen when pressed. The button is disabled while the username is invalid. (Do you notice how we initialize ChatScreen in the NavigationLink? Had we made ChatScreen connect to the server in its init(), it would&#39;ve done so <em>right now</em>!)</li></ol><p>If you wish you can add a little <em>panache</em> to screen. 😉</p><p>Since we’re using SwiftUI’s navigation features, we need to start off with a NavigationView somewhere. ContentView is the perfect spot for this. Change ContentView&#39;s implementation as follows:</p><pre>struct ContentView: View {<br>	@StateObject private var userInfo = UserInfo() // 1<br>	<br>        var body: some View {<br>		NavigationView {<br>			SettingsScreen()<br>		}<br>		.environmentObject(userInfo) // 2<br>		.navigationViewStyle(StackNavigationViewStyle())// 3<br>    }<br>}</pre><ol><li>We initialize an instance of UserInfo and...</li><li>… pass it along as an EnvironmentObject, making it accessible to all succeeding views.</li><li>This is just to make the app not use a columned navigation view on certain screen sizes.</li></ol><p>Now to send the data of UserInfo along with the messages we send to the server. Go to ChatScreenModel (wherever you put it). At the top of the class add the following properties:</p><pre>final class ChatScreenModel: ObservableObject {<br>	private var username: String?<br>	private var userID: UUID?<br><br>	// the rest ...<br>}</pre><p>The ChatModelScreen should receive these values when connecting. It&#39;s not ChatModelScreen&#39;s job to know where this information came from. If, in the future, we decide to change where both username and userID are stored, we can leave ChatModelScreen untouched.</p><p>Change the connect() method to accept these new properties as arguments:</p><pre>func connect(username: String, userID: UUID) {<br>	self.username = username<br>	self.userID = userID</pre><pre>	// etc ...<br>}</pre><p>Finally, in send(text:), we need to apply these new values to the SubmittedChatMessage we&#39;re sending to the server:</p><pre>func send(text: String) {<br>	guard let username = username, let userID = userID else {<br>		return<br>	}<br>	<br>	let message = SubmittedChatMessage(message: text, user: username, userID: userID)<br>	// Everything else ...<br>}</pre><p>Aaaand that’s it for ChatScreenModel. It&#39;s <em>finished</em>. 😙👌</p><p>For the final time, open up ChatScreen.swift. At the top of ChatScreen add:</p><pre>@EnvironmentObject private var userInfo: UserInfo</pre><p>Don’t forget to supply the username and userID to ChatScreenModel when the view appears:</p><pre>private func onAppear() {<br>	model.connect(username: userInfo.username, userID: userInfo.userID)<br>}</pre><p>Now, once again, as practiced: Lean back in that chair and look up at the ceiling. <em>What</em> should the text messages look like? If you’re in no mood for creative thinking, you can use the following View that represents a single received (and sent) message:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fb32b122f3d2d906197e15ef7c36d4e4/href">https://medium.com/media/fb32b122f3d2d906197e15ef7c36d4e4/href</a></iframe><p>It’s not particularly exciting looking. Here’s what it looks like on an iPhone:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/730/1*rYxj694gDM_AKRrVqt2WgQ.png" /></figure><p>(Remember how the server also sends the date of a message? Here it’s used to display the time.)</p><p>Colors and positioning are based on the isUser property that&#39;s passed down by the parent. In this case, that parent is none other than ChatScreen. Because ChatScreen has access to the messages <em>as well</em> as the UserInfo, it&#39;s there where the logic is placed to determine whether the message belongs to the user or not.</p><p>ChatMessageRow replaces the boring Text we used before to represent messages:</p><pre>ScrollView {<br>	ScrollViewReader { proxy in<br>		LazyVStack(spacing: 8) {<br>			ForEach(model.messages) { message in<br>				// This one right here 👇, officer.<br>				ChatMessageRow(message: message, isUser: message.userID == userInfo.userID)<br>					.id(message.id)<br>			}<br>		}<br>		// etc.<br>	}<br>}</pre><p>Welcome to the finish line! You’ve made it all the way here! For the final time, ▶️ run the app and chat away.</p><p>By now you should have a primitive — but fuctioning — chat app. As well as a server handling the incoming and outgoing messages. All written in Swift!</p><p>Congrats! And thank you very much for reading! 🎊</p><p>You can download the final code from <a href="https://github.com/frzi/SwiftChatApp">Github</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*ThHv-3ijagHH19FP1TKzvA.gif" /><figcaption><em>Left: Tiny iPad with dark appearance. Right: Huge iPhone with light appearance.</em></figcaption></figure><h3>Recap</h3><p>Let’s sum up our journey:</p><ul><li>We created a server as a Swift Package executable.</li><li>We used the Vapor web framework to listen to WebSocket connections.</li><li>We programmed the server to receive, parse and send payloads to connected clients.</li><li>We created a basic SwiftUI to send and render messages.</li><li>We used the provided WebSocket APIs in <em>Foundation</em> to provide the communication with the server.</li></ul><p>All that while completely staying within the Swift ecosystem. No extra programming languages, no <a href="https://cocoapods.org/">Cocoapods</a> or anything.</p><h3>Final words</h3><p>Of course, what we created here is only a fraction of a fraction of a complete, production ready chat app and server. We cut a lot of corners to save on time and complexity. Needless to say it should give a pretty basic understanding of how a chat app works.</p><p>Consider the following features to, perhaps, implement yourself:</p><ul><li><strong><em>Multiple channels</em></strong><br>Our server basically accounts for just one channel to chat in. Everyone who connects joins the same party. Modern chat software (e.g. Discord, Slack and Teams) all allow for multiple channels for people to talk in. They even have private chats!</li><li><strong><em>Respectful autoscroll</em></strong><br>You may have noticed the scrollview now <em>always</em> scrolls to the bottom whenever a message is received. This is seriously annoying for users who manually scrolled up to read earlier messages. A respectful chat app only scrolls to the bottom automatically <em>if the scrollview was already at the bottom</em>.</li><li><strong><em>Splice the amount of messages in memory</em></strong><br>Currently, using the ForEach View, we iterate through <em>every</em> message in memory. Modern chat software only keep track of a handful of messages to render, and only load in older messages once the user scrolls up.</li><li><strong><em>Server messages</em></strong><br>It’s common courtesy to announce your arrival whenever you enter a party. A feature you see in all chat software are server-generated messages announcing people joining or leaving the party.</li></ul><h3>Final notes</h3><blockquote><strong>That odd URLSessionWebSocketTask API</strong><br>If you’ve ever worked with WebSockets before, you may share the opinion that Apple’s API for WebSocket’s are quite… non-traditional. You’re certainly not alone on this. Having to constantly rebind the receive handler is just odd. If you think you’re more comfortable using a more traditional WebSocket API for iOS and macOS then I would certainly recommend <a href="https://github.com/daltoniam/Starscream">Starscream</a>. It’s well tested, performant and works on older versions of iOS.</blockquote><blockquote><strong>Bugs bugs bugs</strong><br>This tutorial was written using Xcode 12 beta 5 and iOS 14 beta 5. Bugs appear and disappear between each new beta version. It is unfortunately impossible to predict what will and what won’t work in future (beta) releases.</blockquote><h3>Appendix A: running on physical device</h3><p>The server not only runs on your local machine, it’s only <em>accessible</em> from your local machine. This isn’t a problem when running the app in Simulator (or as macOS app on the same machine). But running the app on a physical device, or on a different Mac, the server will have to be made accessible in your local network.</p><p>To do this, in main.swift of the server code, add the following line <em>directly after</em> initializing the Application instance:</p><pre>app.http.server.configuration.hostname = &quot;0.0.0.0&quot;</pre><p>Now in ChatScreenModel, in the connect(username:userID:) method, you need to change the URL to match your machine&#39;s local IP:</p><pre>let url = URL(string: &quot;ws://127.0.0.1:8080/chat&quot;)!<br>                          //^^this^^^</pre><p>Your machine’s local IP can be found in various ways. Personally I always just open <em>System Preferences &gt; Network</em>, where the IP is directly shown, ready to be selected and copied.</p><p><em>It should be noted that the success rate of this varies between networks. There are a lot of factors (like security) that could prevent this from working.</em></p><p>Thank you so much for reading! If you have any opinions on this piece, thoughts for improvements, or found some errors, please, please, <em>please</em> let me know! I will do my very best to continuously improve this tutorial. 🙂</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=78b34c3dc912" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>