<?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 Victor Baro on Medium]]></title>
        <description><![CDATA[Stories by Victor Baro on Medium]]></description>
        <link>https://medium.com/@victorbaro?source=rss-11fcfb2d5448------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*Kd37OQl0TyfYVkeB00QFkg@2x.jpeg</url>
            <title>Stories by Victor Baro on Medium</title>
            <link>https://medium.com/@victorbaro?source=rss-11fcfb2d5448------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 03 Aug 2026 11:14:36 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@victorbaro/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[Dotted background effect in Metal]]></title>
            <link>https://medium.com/@victorbaro/dotted-background-effect-in-metal-8214673edc9d?source=rss-11fcfb2d5448------2</link>
            <guid isPermaLink="false">https://medium.com/p/8214673edc9d</guid>
            <category><![CDATA[metal]]></category>
            <category><![CDATA[visual-effects]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[shaders]]></category>
            <category><![CDATA[animation]]></category>
            <dc:creator><![CDATA[Victor Baro]]></dc:creator>
            <pubDate>Tue, 21 Apr 2026 12:49:38 GMT</pubDate>
            <atom:updated>2026-04-21T12:49:38.607Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6b7AlzyBtnsj8kECD0yh2A.gif" /></figure><p>Last week I had the pleasure of attending Try! Swift Tokyo as a speaker — the 10th edition of one of the best Swift conferences in the world. Between the incredible talks, the hallway conversations, and the energy of being surrounded by passionate developers, it was an experience I won’t forget easily.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vBbnSWRGs7-YtQR9Uo3Fgw.jpeg" /><figcaption>Try! Swift Tokyo 2026</figcaption></figure><p>I was selected to run a workshop on SwiftUI + Metal. I prepared four hands-on examples designed to give attendees a real feel for what Metal shaders can do — not just “here’s a cool effect”, but a genuine understanding of how it works from the ground up.<strong> This post covers the first one: an animated dotted background that reacts to touch.</strong></p><h3>Why this example?</h3><p>I wanted to start somewhere that felt magical but stayed focused. The shader is applied to the entire background, and everything — every dot, every animation — is drawn in code on the GPU. There’s no UIKit, no SpriteKit, no assets. Just math.</p><p>It supports three modes:</p><ul><li>Glow — dots near your finger grow and brighten</li><li>Attraction — dots pull toward your touch like a magnet</li><li>Repulsion — dots scatter away from wherever you press</li></ul><blockquote>Before we start, you can find a deep dive article with interactive examples at <a href="http://metal.graphics/dotted-background-interactive">metal.graphics/dotted-background-interactive</a> — This medium post highlights the most important parts.</blockquote><h3>The key ideas</h3><h4>1. Shaders run per pixel, in parallel</h4><p>A Metal shader isn’t a loop. It’s a function that runs simultaneously for every pixel on screen. Your job is to answer one question for each pixel: <em>what colour should I be?</em></p><pre>Color.black<br>    .colorEffect(<br>        ShaderLibrary.dottedBackground(<br>            .boundingRect,<br>            .float2(touchPosition.x, touchPosition.y),<br>            .float(Float(mode.rawValue)),<br>            .float(Float(intensity))<br>        )<br>    )</pre><p>SwiftUI passes your uniforms — canvas size, touch position, mode, intensity — directly to the GPU on every frame.</p><h4>2. Drawing a circle with SDF</h4><p>There’s no “draw circle here” API. Instead, for every pixel, you ask: am I inside or outside the circle?</p><pre>float dist = radius - length(uv - center);<br>float circle = smoothstep(-0.02, 0.02, dist);</pre><p><em>dist</em> is positive inside, negative outside. <em>smoothstep</em> converts that into a smooth 0–1 mask — which gives you antialiased edges.</p><h4>3. Tiling with fract()</h4><p>One dot is easy. Four hundred dots is just <em>fract()</em>.</p><pre>float2 cellUV = fract(uv * float2(cols, rows));<br>float dist = radius - length(cellUV - 0.5);</pre><p><em>fract()</em> strips the integer part of a number, leaving only the fractional remainder. Scale UV by 40, take fract, and you get 40 columns each with their own fresh 0–1 coordinate space. The SDF circle runs identically inside every “cell”.</p><h4>4. Connecting dots to touch with floor()</h4><p><em>fract()</em> tells a pixel where it is inside its cell. But to react to touch, each dot needs to know where it sits in world space — so it can measure its distance to your finger.</p><p>That’s <em>floor()</em>:</p><pre>float2 scaled = uv * grid;<br>float2 cellIndex = floor(scaled);<br>float2 dotWorld = (cellIndex + 0.5) / grid; // dot centre in UV space<br>float touchDist = length(dotWorld - touchUV);<br>float influence = 1.0 - smoothstep(0.0, influenceRadius, touchDist);</pre><p>influence is 1 at the touch, fading smoothly to 0 at the edge of the radius. Feed that into mix() to scale radius and brightness.</p><h4>5. Moving dots — a coordinate trick</h4><p>Here’s the part that surprises people: there are no dots to move. A dot isn’t an object. It’s a question asked of every pixel. You can’t push it.</p><p>What you can do is shift the dot’s logical center before asking the question:</p><pre>// Repulsion - push center away<br> dotCenter -= dir * (influence * maxDisplacement);<br>// Attraction - one sign flip<br> dotCenter += dir * (influence * maxDisplacement);</pre><p>The GPU still renders everything in place. But by moving where the dot thinks its center is, the effect looks exactly like motion.</p><h4>6. Fixing clipping at cell boundaries</h4><p>One subtle issue: when a dot is displaced far enough, it crosses into a neighbouring cell and gets clipped. The fix is a 3×3 neighbourhood check — for each pixel, evaluate 9 candidate dots and keep the brightest one.</p><pre>for (int dy = -1; dy &lt;= 1; dy++) {<br> for (int dx = -1; dx &lt;= 1; dx++) {<br> // evaluate neighbor&#39;s dot, keep best result<br> }<br>}</pre><p>Nine times more math per pixel — but the GPU handles it without breaking a sweat. This is exactly what it’s built for.</p><h3>The result</h3><p>A 130-line Metal shader that produces a fully interactive dot field, responsive to touch on iOS and hover on macOS, with three distinct behaviors controlled by a single mode uniform.</p><p>The complete shader, all six build steps with interactive previews, and the full SwiftUI wiring are at <a href="http://metal.graphics/dotted-background-interactive">metal.graphics/dotted-background-interactive</a></p><p>Each step is runnable in the browser — you can move your cursor over the previews to interact with the shader live, and see exactly how each concept layers on top of the last.</p><p>If you’re curious about Metal shaders more broadly, the full course is at <a href="https://metal.graphics">metal.graphics</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8214673edc9d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[MetalGraph: a new way of working with Metal shaders for SwiftUI]]></title>
            <link>https://medium.com/@victorbaro/metalgraph-a-new-way-of-working-with-metal-shaders-for-swiftui-bed1cf1a2b81?source=rss-11fcfb2d5448------2</link>
            <guid isPermaLink="false">https://medium.com/p/bed1cf1a2b81</guid>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[app-development]]></category>
            <category><![CDATA[metal]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[macos-app]]></category>
            <dc:creator><![CDATA[Victor Baro]]></dc:creator>
            <pubDate>Mon, 05 Jan 2026 22:45:13 GMT</pubDate>
            <atom:updated>2026-01-05T22:45:13.308Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QF441JqBckHR1U-1i-Yk4g.png" /></figure><p>I’ve been learning Metal and working on <a href="https://metal.graphics">metal.graphics</a> since early last year.</p><p>While going through examples, writing content, and experimenting with different effects, I kept running into the same issue over and over: even with SwiftUI previews, the feedback loop when working on Metal shaders is still quite disruptive.</p><p>Previews are powerful, and they’ve come a long way, but when you’re fine-tuning shader values or experimenting with animations, the constant reloading adds a ton of friction. And when you’re learning or exploring, that friction adds up quickly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kqceVYFKIa4rxOKaNMEppQ.png" /><figcaption>Blender node graph editor</figcaption></figure><p>I am not a 3D expert at all, but I do like to play with 3D tools like Blender or Spline. Blender is an amazing software, and at some point in the past they added a node graph. I did have so much fun playing with it, the realtime and ease of use makes for a great experience. The node editor makes data flow explicit. You’re not just writing code, you’re seeing how values move through operations. That mental model maps very well to how shaders actually work.</p><p>The goal was simple: preview and tweak Metal shaders in real time, without constantly rebuilding or switching context. I wanted something where I could focus on the shader itself, not on the mechanics around it.</p><p>MetalGraph is my attempt at bringing that kind of experience to Metal.</p><h3>What MetalGraph is (and isn’t)</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*yujYLHl8AUnMuLkWYL3ohQ.gif" /><figcaption>Realtime interaction inside MetalGraph</figcaption></figure><p>MetalGraph is a node graph macOS app where you can design Metal shaders and see the result immediately as you work.</p><p>You connect nodes, tweak values, change structure, and the preview updates in real time. The focus is on keeping the feedback loop tight while you’re exploring ideas.</p><p>It’s not meant to replace writing Metal code.</p><p>The end goal is still to understand what the shader is doing and eventually write or refine the code yourself. The node graph is a way to reason about structure and data flow visually, especially when you’re learning or experimenting.</p><h3>A simple example</h3><p>A very basic example might be something like building a radial gradient.</p><p>Instead of starting with a full shader function and iterating through rebuilds, you can visually connect:</p><ul><li>a position or UV node</li><li>a distance calculation</li><li>a smoothstep</li><li>a color mix</li></ul><p>By adjusting parameters like radius or falloff, you immediately see how they affect the final result.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tcLb1i94tPr_VF_yQaL1Kw.png" /></figure><p>Changing the structure, for example introducing a distortion (noise) or time-based offset, is just as immediate.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m5gunT0MJf3gHdhPDDmhFQ.png" /></figure><p>That immediacy turned out to be a big deal for me. Even with rough early versions of the app, it made learning and experimentation noticeably faster.</p><h3>From prototype to something usable</h3><p>Around July I had something that worked. It was rough, unfinished, but already useful enough that I kept using it for learning and experimentation.</p><p>As many of us know, the last 10% of an app is the hardest. Around the same time, we had some intense months at <a href="https://panels.store">Panels working on other extremely exciting projects</a>, so MetalGraph had to take a back seat for a while.</p><p>During the Christmas break, I decided to give it a final push. I cleaned things up, fixed rough edges, and focused on making it usable by others, not just by me.</p><h3>Current state</h3><p>MetalGraph is now available on macOS.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/1*K6woMGDz-eVMDTtC0X13zg.gif" /><figcaption>Realtime shader preview</figcaption></figure><p>I recorded a short overview video where I walk through the app and build a few very simple examples step by step:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FFH2GdFuk9nI%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DFH2GdFuk9nI&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FFH2GdFuk9nI%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="640" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/dbf450e0055b18baa48bacc253e730e4/href">https://medium.com/media/dbf450e0055b18baa48bacc253e730e4/href</a></iframe><p>The app can be downloaded and used in trial mode. In trial mode you can access all built-in examples and tweak node values, but you can’t add or remove nodes. Unlocking the full version requires purchasing a license.</p><p>You can find more details and download the app here:</p><p><a href="https://metal.graphics/app">MetalGraph - Visual Shader Editor for macOS</a></p><h3>What’s next</h3><p>This is still early.</p><p>My main goal right now is to get feedback from people who are already working with Metal or learning it. I’m especially interested in whether this kind of visual workflow helps others the same way it helped me.</p><p>If you try it and have thoughts, good or bad, I’d love to hear them.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bed1cf1a2b81" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SDF in Metal: Adding the Liquid to the Glass]]></title>
            <link>https://medium.com/@victorbaro/sdf-in-metal-adding-the-liquid-to-the-glass-69abd57e2151?source=rss-11fcfb2d5448------2</link>
            <guid isPermaLink="false">https://medium.com/p/69abd57e2151</guid>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[metal]]></category>
            <category><![CDATA[shaders]]></category>
            <category><![CDATA[liquid-metal]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <dc:creator><![CDATA[Victor Baro]]></dc:creator>
            <pubDate>Sun, 07 Sep 2025 22:27:30 GMT</pubDate>
            <atom:updated>2025-09-07T22:27:30.757Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*xIiHS3sQSTsGuteO.jpg" /></figure><p>In the past I covered how to <a href="https://medium.com/@victorbaro/implementing-a-refractive-glass-shader-in-metal-3f97974fbc24">create a <em>“simple”</em> refractive glass effect</a> using Metal shaders, as well as <a href="https://medium.com/@victorbaro/recreating-a-laminated-glass-effect-3a550e64f6eb">how to apply interesting overlay glass effects</a>.</p><p>Today we’re going to leave the glass behind and focus instead on the <em>liquid</em> part of it. This is the piece that makes Apple’s <a href="https://developer.apple.com/documentation/swiftui/glasseffectcontainer">g<em>lassEffectContainer</em></a> effect in iOS 26 look alive, organic, and “gooey”.</p><h3><strong>What’s an SDF?</strong></h3><p>SDF stands for <strong>Signed Distance Function</strong>. It’s a way of representing shapes mathematically.</p><p>For any point in space (or pixel on screen), an SDF tells you:</p><ul><li>Negative value → you’re inside the shape</li><li>Zero → you’re exactly on the surface</li><li>Positive value → you’re outside the shape</li></ul><p>For example, a circle SDF looks like this:</p><pre>float circleSDF(float2 p, float radius) {<br>  return length(p) - radius;<br>}</pre><p>If <strong>p</strong> is exactly radius units away from the center, the function returns 0 (on the circle). Inside the circle it returns negative, and outside positive.</p><p>Plotting that circle’s raw SDF data produces the following result (see image below). Pixels inside radius are black (negative values) and pixels outside the radius are gray (positive values):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4ZdNZef-fAkTsyQZUX24oA.png" /></figure><p>The magic is that you can combine these distance values with simple math (min, max, etc.) to create complex shapes and effects.</p><h3>Boolean operations</h3><p>First let’s set the stage. We are going to draw two circles:</p><pre>[[ stitchable ]] half4 sdfBooleanOps(<br>    float2 position,<br>    half4 color,<br>    float2 size,<br>    float smoothness // 0=hard, 0.1=smooth<br>) {<br>  float2 uv = position / size;<br>  float2 centered = uv - 0.5;<br>  // Define two overlapping shapes<br>  float circle1 = length(centered - float2(-0.1, 0.0)) - 0.15; // Left circle<br>  float circle2 = length(centered - float2(0.1, 0.0)) - 0.15; // Right circle<br>  float result = unionSDF(circle1, circle2); // unionSDF is explained below<br><br>  // For better visualization, let&#39;s mask the 2 shapes, so any values outside the circle are +1 (white) and all values insdide are black (-1). The easiest way to do this is by using step (or smoothStep).  <br>  result = step(0.1, result);  <br><br>  return half4(result, result, result, 1.0);<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1016/1*GvNs3z6CWv3mAj8fwS6TDw.png" /></figure><p><strong>Boolean operations on distance fields</strong></p><p>Signed Distance Functions (SDFs) become powerful because you can combine them with boolean operations. The idea is simple: you have two distance fields (d1 and d2), and you define how they interact:</p><pre>float unionSDF(float d1, float d2) {<br>  return min(d1, d2); // Closest surface wins<br>}<br><br>float intersectionSDF(float d1, float d2) {<br>  return max(d1, d2); // Farthest surface wins<br>}<br><br>float differenceSDF(float d1, float d2) {<br>  return max(d1, -d2); // Remove d2 from d1<br>}</pre><ul><li>Union: two shapes combined into one.</li><li>Intersection: keep only the overlap.</li><li>Difference: subtract one shape from another.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1016/1*GvNs3z6CWv3mAj8fwS6TDw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/990/1*Ig4ypdBNhOGsq6US1Qbm2Q.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1018/1*YaE393Mh2AMz7DAAkH76rA.png" /></figure><p>This is the “hard” boolean world. But what makes Apple’s glassContainer so magical is the smooth version.</p><p><strong>The magic of SDF: Smooth functions</strong></p><p>Take the union, for example. Instead of snapping two shapes together, we can create a gradual blend. This is where smoothUnion comes in:</p><pre>// Smooth versions for organic blending<br>float smoothUnion(float d1, float d2, float smoothness) {<br>  float h = max(smoothness - abs(d1 - d2), 0.0) / smoothness;<br>  return min(d1, d2) - h * h * smoothness * 0.25;<br>}</pre><p>Here’s what’s happening. If the shapes are far apart, it behaves like a normal union. As they get closer, the seam becomes rounded and fluid-like. Last, the smoothness parameter controls how wide that blending region is.</p><p>In terms of the maths, the hardest part to digest is “h * h * smoothness * 0.25”. It’s actaully pretty simple, it subtracts a small “smoothing offset” to round the union instead of keeping it sharp. You can play with that value and adjust it to your needs, 0.25 works pretty nicely.</p><p>Here is an example of how different values of smoothness affect the 3 boolean operations.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*lQmB1Kirv9z0jN1ZMNPlug.gif" /></figure><h3>Applying the effect</h3><p>That’s pretty much it. Once you understand how to blend shapes together, you can apply to any shape.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/672/1*Uwgmfkik-N624TvWu0ti2A.gif" /></figure><p>To master and illustrate those concepts, in full the <a href="https://metal.graphics">metal shaders course</a>, there are some interesting examples, like a lava lamp effect, some draggable light balls, and my personal favorite… a dripping goeey button.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*Renoww_el-KgJOxEDwi5RA.gif" /><figcaption>Lava lamp simulator</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/1*td8Mafnwd_3o3GBTFhsE8A.gif" /><figcaption>Draggable animated light blobs</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*mnCZH6c7WonaztGahHosPg.gif" /><figcaption>Dripping button, just because we can</figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=69abd57e2151" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Custom SwiftUI transitions with Metal]]></title>
            <link>https://medium.com/@victorbaro/custom-swiftui-transitions-with-metal-680d4e31a49b?source=rss-11fcfb2d5448------2</link>
            <guid isPermaLink="false">https://medium.com/p/680d4e31a49b</guid>
            <category><![CDATA[computer-graphics]]></category>
            <category><![CDATA[animation]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[metal]]></category>
            <category><![CDATA[swiftui]]></category>
            <dc:creator><![CDATA[Victor Baro]]></dc:creator>
            <pubDate>Mon, 18 Aug 2025 04:03:36 GMT</pubDate>
            <atom:updated>2025-08-18T16:58:22.199Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*RbZ8pIts96Os2ankhokPGw.gif" /><figcaption>One of the transitions covered in this post</figcaption></figure><p><strong>Distortion effects</strong> are fun. They are a bit limited but you can still get a lot from them. In <a href="https://www.metal.graphics/chapter9-time-animation">Metal.graphics chapter 9</a> (challenge 2), I explored an interactive ripple effect. This is different from Chapter’s 4 because it uses more than one wave and you can tap+drag to keep generating waves. The final result is visually stunning and very satisfying, although arguably not very useful in regular conditions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/1*GnhkWMBGItsyA8jWnr6kNw.gif" /><figcaption>Ripple effect on tap and drag from Metal.graphics</figcaption></figure><p>One way we can try to make this (or any other shader really) a bit more useful is by using it for transitioning between views/images.</p><p>SwiftUI makes creating custom transitions very easy. Buckle up, this post might be long. I will skip some parts for simplicity, you can find the full code for all working examples is in the <a href="https://metalgraphics.gumroad.com/l/project">full downloadable project</a>, along with all other Metal examples.</p><p>Let’s build some cool transitions.</p><blockquote>In this post I am going to focus on distortion effects. I am not going to touch color effects or layer effects but you can use them as well to build custom transitions.</blockquote><h3>Initial approach</h3><p>My initial approach was to repurpose a similar code, adding progress as parameter. At that point I did not fully understand SwiftUI transitions I implemented the following:</p><pre>extension AnyTransition {<br>    static func simpleRipple(from point: CGPoint) -&gt; AnyTransition {<br>        .asymmetric(<br>            insertion: .init(RippleInsertionTransition(tapLocation: point)),<br>            removal: .opacity<br>        )<br>    }<br>}<br><br>struct RippleInsertionTransition: Transition {<br>    var tapLocation: CGPoint<br>    <br>    func body(content: Content, phase: TransitionPhase) -&gt; some View {<br>        // For insertion: phase.value goes -1.0 → 0.0<br>        // Convert to: 0.0 → 1.0 for reveal progress<br>        let progress = 1.0 + phase.value<br>        <br>        return content<br>            .visualEffect { content, proxy in<br>                content.distortionEffect(<br>                    ShaderLibrary.rippleDistortion(<br>                        .float2(Float(proxy.size.width), Float(proxy.size.height)),<br>                        .float(Float(progress)),<br>                        .float2(Float(tapLocation.x), Float(tapLocation.y))<br>                    ),<br>                    maxSampleOffset: CGSize(width: 0, height: 0)<br>                )<br>            }<br>            .clipShape(CircularRevealShape(center: tapLocation, progress: progress))<br>    }<br>}<br><br>struct CircularRevealShape: Shape {<br>    var center: CGPoint<br>    var progress: CGFloat<br>    <br>    var animatableData: CGFloat {<br>        get { progress }<br>        set { progress = newValue }<br>    }<br>    <br>    func path(in rect: CGRect) -&gt; Path {<br>        let maxRadius = sqrt(pow(rect.width, 2) + pow(rect.height, 2))<br>        let currentRadius = progress * maxRadius * 1.4<br>        <br>        var path = Path()<br>        path.addEllipse(in: CGRect(<br>            x: center.x - currentRadius,<br>            y: center.y - currentRadius,<br>            width: currentRadius * 2,<br>            height: currentRadius * 2<br>        ))<br>        return path<br>    }<br>}<br><br>struct SimpleRippleTestView: View {<br>    @State private var currentIndex = 0<br>    @State private var tapLocation: CGPoint = CGPoint(x: 150, y: 150)<br>    <br>    let images: [ImageResource] = [.unsplash3, .unsplash4, .unsplash5]<br>    <br>    var body: some View {<br>        ZStack {<br>            ForEach(images.indices, id: \.self) { index in<br>                if currentIndex == index {<br>                    Image(images[index])<br>                        .resizable()<br>                        .aspectRatio(contentMode: .fill)<br>                        .frame(width: 600, height: 300)<br>                        .transition(.simpleRipple(from: tapLocation))<br>                        <br>                }<br>            }<br>            Text(&quot;Ripple Metal Transition&quot;)<br>                .font(.largeTitle)<br>                .fontWeight(.black)<br>                .foregroundStyle(.black)<br>                .padding()<br>                .blendMode(.overlay)<br>                .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 12))<br>        }<br>        .onTapGesture { location in<br>            tapLocation = location<br>            withAnimation(.easeInOut(duration: 2)) {<br>                currentIndex = (currentIndex + 1) % images.count<br>            }<br>        }<br>        .clipped()<br>        .cornerRadius(20)<br>        .shadow(radius: 10)<br>    }<br>}</pre><p>This works and produces the result I show at the top on the very first video. Let’s analyze what it does and go over some “problems” of this approach.</p><p>The ripple transition is made using an asymmetric transition.</p><pre>.asymmetric(<br>            insertion: .init(RippleInsertionTransition(tapLocation: point)),<br>            removal: .opacity<br>        )</pre><p>The way asymmetric transitions work on SwiftUI is by defining an insertion transition (“how do we add the new image?”) and a removal transition( “how do we remove the old image?”).</p><p>The removal (old image) in this case is using a simple opacity. The insertion is the one using the magic:</p><pre>return content<br>            .visualEffect { content, proxy in<br>                content.distortionEffect(<br>                    ShaderLibrary.rippleDistortion(<br>                        .float2(Float(proxy.size.width), Float(proxy.size.height)),<br>                        .float(Float(progress)),<br>                        .float2(Float(tapLocation.x), Float(tapLocation.y))<br>                    ),<br>                    maxSampleOffset: CGSize(width: 0, height: 0)<br>                )<br>            }<br>            .clipShape(CircularRevealShape(center: tapLocation, progress: progress))</pre><p>The transition itself is made out of 2 parts. The first part is the ripple effect (made using a distortionEffect) and the second is the clipShape.</p><p>SwiftUI (unless told otherwise) places the new image on top of the old one. What the clipShape is doing is masking the top image as the ripple progresses.</p><p>This approach visually works because in a 2sec animation is almost impossible to tell the trick, but if we slow it down considerably we see it is not perfect. The ripple has a clean round cut. Ideally it will be really nice to deform the image being removed as well.</p><h3>A better approach</h3><p>I honestly couldn’t find a nice way to do a transition without a clipShape that worked the way that I wanted.</p><blockquote>Thanks to google, I was able to find this incredibly nice post by Pavel.</blockquote><blockquote><a href="https://nerdyak.tech/development/2023/06/16/distortionEffect-with-Metal-shaders-for-better-transitions.html">https://nerdyak.tech/development/2023/06/16/distortionEffect-with-Metal-shaders-for-better-transitions.html</a> <br>Please go read it, it is short and sweet. At the very least, please watch the first video.</blockquote><p>He provides the code for his amazing transition, but in all honestly I still could not figure out how he made it work. I did not know how to connect the progress with the direction just by looking at the code.</p><p>So I made myself a playground. <a href="https://gist.github.com/victorBaro/23b8172c87cc3c2d06801014a14b293c">Here is the gist if you are interested.</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/648/1*B-WZipAIvP1xQ8kNZKgWSw.gif" /></figure><p>First, let’s understand the tricky Metal code that happens inside the first if block.</p><pre>[[ stitchable ]] float2 slideAwayShader(float2 position, float2 size, float time, float direction) {<br>    float2 c = size/2;<br>    float2 v = position - c;<br>    <br>    float f = (direction &gt; 0 ? position.x : (size.x - position.x) )/size.x;<br>    <br>    if ( time &gt; f ) {<br>        float mul = (time-f)/(1-f);<br>        return c + v*mul;<br>    }<br>    else {<br>        return float2(-1, -1);<br>    }<br>}</pre><p>Those 2 lines are where the magic happens. The math trick is to move each pixel smoothly from the <strong>center</strong> of the view back out to its original position (as in squeezing the pixels).</p><ul><li>mul = (time — f) / (1 — f)</li></ul><p>Here, f is the moment (between 0 and 1) when this pixel is “allowed” to start moving. Before that, the pixel doesn’t exist. After that, time keeps growing toward 1. This formula maps that time window [f … 1] into a clean [0 … 1] scale. In other words: when time == f, mul = 0; when time == 1, mul = 1. Everything in between gives a smooth fraction.</p><ul><li>return c + v * mul</li></ul><p>c is the center point, and v is the vector from the center to the pixel’s original position. So c + v*0 = the center, and c + v*1 = the pixel’s true place. Multiplying v by mul means “slide outward from the center along the line to where the pixel belongs.” As mul goes from 0 to 1, the pixel travels along that line, giving the effect of expanding out from the center column toward the edges.</p><p>Understanding the maths and playing with the playground, it was clear a bit more clear that Pavel was using the same shader in an asymmetric transition with:</p><ul><li>insertion (left to right) with progress moving from 0 to 1</li><li>removal (right to left) with progress moving from 1 to 0</li></ul><p>Understanding this was key to implement the final transition.</p><pre>import SwiftUI<br><br>struct SimpleSlideAwayRipple: View {<br>    @State private var currentIndex = 0<br>    <br>    let images: [ImageResource] = [.unsplash3, .unsplash4, .unsplash5]<br>    <br>    var body: some View {<br>        VStack(spacing: 20) {<br>            Text(&quot;Slide Away Ripple Transition&quot;)<br>                .font(.largeTitle)<br>                .fontWeight(.black)<br>                .foregroundStyle(.black)<br>            <br>            ZStack {<br>                ForEach(images.indices, id: \.self) { index in<br>                    if currentIndex == index {<br>                        Image(images[index])<br>                            .resizable()<br>                            .aspectRatio(contentMode: .fill)<br>                            .frame(width: 350, height: 350)<br>                            .transition(.slideAwayRipple)<br>                            .onTapGesture {<br>                                withAnimation(.easeInOut(duration: 3)) {<br>                                    currentIndex = (currentIndex + 1) % images.count<br>                                }<br>                            }<br>                    }<br>                }<br>            }<br>            .clipShape(RoundedRectangle(cornerRadius: 20))<br>            .shadow(radius: 10)<br>            <br>            Text(&quot;Tap to transition&quot;)<br>                .font(.caption)<br>                .foregroundColor(.secondary)<br>        }<br>        .padding()<br>    }<br>}<br><br>// Slide Away Ripple transition<br>struct SlideAwayRippleTransition: Transition {<br>    let direction: Float<br>    func body(content: Content, phase: TransitionPhase) -&gt; some View {<br>        let progress = direction == 1<br>        ? Float(1.0 + phase.value) // -1→0 becomes 0→1 for insertion<br>        : Float(1.0 - phase.value)<br>        <br>        <br>        return content<br>            .visualEffect { content, proxy in<br>                content.distortionEffect(<br>                    ShaderLibrary.slideAwayRipple(<br>                        .float2(Float(proxy.size.width), Float(proxy.size.height)),<br>                        .float(progress),<br>                        .float(direction)<br>                    ),<br>                    maxSampleOffset: CGSize(width: 0, height: 0)<br>                )<br>            }<br>    }<br>}<br><br>extension AnyTransition {<br>    static var slideAwayRipple: AnyTransition {<br>        .asymmetric(insertion: .init(SlideAwayRippleTransition(direction: 1)),<br>                    removal: .init(SlideAwayRippleTransition(direction: -1)))<br>    }<br>}<br><br>#Preview {<br>    SimpleSlideAwayRipple()<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*uslvLUDs3Zgn-cWGot1jhw.gif" /><figcaption>Pavel’s metal transition</figcaption></figure><h3>Improving my ripples</h3><p>Thanks to Pavel’s approach, I was now on my way to improving my ripples, no more masking, no more fading out.</p><p>I followed a very similar approach, using direction and progress and the following code did the job.</p><pre>[[stitchable]] float2 liquidWave(<br>    float2 position,<br>    float2 size,<br>    float progress,  // 0.0 to 2.0 (1.0 = identity)<br>    float direction<br>) {<br>    float2 center = size / 2.0;<br>    float2 fromCenter = position - center;<br>    <br>    // Calculate threshold like slide-away but based on distance from center<br>    float dist = length(fromCenter) / length(center);<br>    float f = direction &gt; 0 ? dist : (1.0 - dist);<br>    <br>    // Reveal phase: 0 → 1<br>    if (progress &gt; f) {<br>        float revealAmount = (progress - f) / (1.0 - f);<br>        <br>        // Liquid wave distortion<br>        float wave1 = sin(position.x * 0.02 + progress * 8.0) * 15.0;<br>        float wave2 = sin(position.y * 0.03 + progress * 6.0) * 10.0;<br>        float wave3 = sin(dist * 20.0 - progress * 12.0) * 8.0;<br>        <br>        float2 waveOffset = float2(wave1 + wave3, wave2 + wave3) * (1.0 - revealAmount);<br>        <br>        return position + waveOffset;<br>    } else {<br>        return float2(-1, -1);<br>    }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*qHukJ3WjhpZhhWsMvMSDPw.gif" /></figure><p>In this case I did not bother to pass in the tap location, it is always computed from the center, but we get the idea.</p><p>Since I was already here, I created a couple more transitions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*H8_l1yGHaYp1h2pytrqp2w.gif" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*6JUtjGtp3C-MWHD1UE7H-g.gif" /></figure><p>That’s all for today. If you create any new transitions, ping me, I’d love to see them!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=680d4e31a49b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Recreating a Laminated Glass effect]]></title>
            <link>https://medium.com/@victorbaro/recreating-a-laminated-glass-effect-3a550e64f6eb?source=rss-11fcfb2d5448------2</link>
            <guid isPermaLink="false">https://medium.com/p/3a550e64f6eb</guid>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[shaders]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[metal]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Victor Baro]]></dc:creator>
            <pubDate>Fri, 01 Aug 2025 21:50:40 GMT</pubDate>
            <atom:updated>2025-08-19T23:40:32.022Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Fg5HcQSWSlArQm9jSDU2JA.png" /></figure><p><a href="https://medium.com/@victorbaro/implementing-a-refractive-glass-shader-in-metal-3f97974fbc24">On my previous post</a> I explored how to create a simple yet convincing glass effect using metal shaders. Let’s keep the glass theme going, it’s Apple’s fault anyway.</p><p>I use <a href="https://en.eagle.cool/">Eagle app</a> to keep a folder with images for inspiration and ideas I’d like to (some day…) implement. I recently stumbled upon the following image — I am sorry, I don’t know what app is this. If you are reading and you know the app, will appreciate you letting me know so I can properly reference it.</p><blockquote><a href="https://medium.com/u/062b8f3caeeb">Maximilian Götzfried (ZENTI)</a> Found the app! Find it here -&gt; <a href="https://apps.apple.com/app/photo-editor-ai-photo-enhancer/id6503194302?platform=iphone">Photo Editor: AI Photo enhancer</a></blockquote><figure><img alt="Vertical glass effect — unknown app" src="https://cdn-images-1.medium.com/max/1024/1*yyDUAoAREOm9xvXXhGRqww.png" /></figure><p>In this post I am going to try to recreate this Laminated Glass effect using metal shaders.</p><p><em>Same as my previous post, this post assumes some basic metal knowledge. I also do not plan to focus on the SwiftUI parts. If you want the full code, it is part of my </em><a href="https://metalgraphics.gumroad.com/l/project"><em>metal graphics xcode project</em></a><em>.</em></p><h3>Breaking it down</h3><p>The effect is actually pretty simple, although it took me a while to understand what it does.</p><p>Imagine the background is split into vertical strips (or <em>columns</em>). Each column behaves like a piece <strong>magnifying glass</strong> that slightly distorts what’s behind it.</p><p>So, rather than sampling the layer directly at position.x, we want to <strong>sample a <em>wider</em> region and squish it back into the space of that column</strong>. This is the trick.</p><p>Implementation will then look something like this:</p><ul><li>Divide the view into vertical columns (columnCount)</li><li>For each column, stretch its contents by a stretchFactor (e.g., 1.5 = 150%)</li><li>Sample from that wider region to get the distorted result</li></ul><p>To implement this, we need:</p><ul><li>A way to compute which column the current pixel belongs to</li><li>A normalized position inside that column (0…1)</li><li>A stretch-adjusted sampling coordinate</li></ul><h3>First version (the verbose one)</h3><p>Here’s the more explicit version, where everything is spelled out step by step:</p><pre>[[ stitchable ]] half4 verticalGlass(<br>    float2 position,<br>    SwiftUI::Layer layer,<br>    float2 size,<br>    float columnCount,      // ~20<br>    float stretchFactor     // 1.5 = 150% width<br>) {<br>    float columnWidth = size.x / columnCount;<br>    int columnIndex = int(position.x / columnWidth);<br>    float columnStart = float(columnIndex) * columnWidth;<br>    <br>    float positionInColumn = (position.x - columnStart) / columnWidth;<br>    <br>    float sampleWidth = columnWidth * stretchFactor;<br>    float sampleStart = columnStart - (sampleWidth - columnWidth) * 0.5;<br>    <br>    float sampleX = sampleStart + positionInColumn * sampleWidth;<br>    float2 samplePos = float2(sampleX, position.y);<br>    <br>    return layer.sample(samplePos);<br>}</pre><p>As explained above, the main trick here was to calculate sampleX:</p><pre>float sampleX = sampleStart + positionInColumn * sampleWidth;</pre><p>Breaking it down:</p><ul><li>positionInColumn is a value from 0 to 1 — it tells us <em>where</em> we are inside the current column.</li><li>sampleWidth is the <em>wider</em> area we want to sample from (due to the stretch).</li><li>sampleStart is where that wider sampling area begins (it’s centered on the original column).</li><li>So, positionInColumn * sampleWidth moves us across the wider region.</li><li>Adding that to sampleStart gives us the final x coordinate to sample from.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/1*bzhjP8F_2J8SgDPAdLJEyw.gif" /></figure><p>As you can see on this gif, the result is pretty good. You can achieve nice effects by tweaking both properties.</p><h3>Enter Fract</h3><pre>fract(x) = x - floor(x)</pre><p>A few months ago I only knew that fract gives us the fractional part of a float. Turns out it is incredibly useful when doing shaders, especially when you try to replicate something multiple times.</p><p>This is covered in detail in <a href="https://www.metal.graphics/chapter5-procedural-patterns">Procedural Patterns</a> chapter from metal.graphics.</p><p>In short, let’s say we want to repeat/tile a texture every 100 pixels. We just need to do:</p><p>float2 uv = fract(position / 100.0);</p><p>For our case, we can rewrite the positionInColumn as:</p><pre>float positionInColumn = (position.x - columnStart) / columnWidth;<br><br>// Rewrite using fract<br>float positionInColumn = fract(position.x / columnWidth); // 0–1</pre><p>In fact, fract makes it so easy that we can now apply it to both x and y for a square tiled effect (not just vertical).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/768/1*kZfoFfDwTW690JSCqup5AA.gif" /></figure><h3>One step further</h3><p>At this point I think we can celebrate 🎉, the effect was successfully replicated. However, I wanted to go one step further and try to apply our learnings from the last post (remember (1 — r²)?).</p><p>Basically my plan was to introduce some falloff on the sides of each “vertical” glass, as if each glass was curved. The result is pretty interesting, makes the animation feel more organic, almost like flowing water.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/614/1*9Y1-IJnZblZYmFvGqwtQ8Q.gif" /><figcaption>Vertical curved glass</figcaption></figure><p><strong>Do you think you can replicate it?</strong></p><p>Talking about replicating, I am looking for more stuff to replicate. If you have ideas, send them my way. And thanks for reading.</p><p>PS: remember that you can apply visualEffect to whole swiftUI views, not just images. That’s how the header image was made, with visualEffect applied to a ZStack containing an image + text</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3a550e64f6eb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Implementing a Refractive Glass Shader in Metal]]></title>
            <link>https://medium.com/@victorbaro/implementing-a-refractive-glass-shader-in-metal-3f97974fbc24?source=rss-11fcfb2d5448------2</link>
            <guid isPermaLink="false">https://medium.com/p/3f97974fbc24</guid>
            <category><![CDATA[shaders]]></category>
            <category><![CDATA[ios-26]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[apple]]></category>
            <category><![CDATA[liquid-glass]]></category>
            <dc:creator><![CDATA[Victor Baro]]></dc:creator>
            <pubDate>Sun, 27 Jul 2025 20:23:12 GMT</pubDate>
            <atom:updated>2025-08-22T18:22:41.856Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/952/1*v1vJ_YYiLC_mRaVO2lE5jg.png" /></figure><p><em>This post explores how to build a simple but convincing refractive glass material in Metal. It assumes you have some familiarity with fragment shaders and SwiftUI integration. If you’re new to this, I recommend checking out Chapters 1 and 2 of the </em><a href="https://metal.graphics"><em>Metal for SwiftUI course</em></a><em> to get up to speed.</em></p><h3>Why Glass is difficult</h3><p>Glass seems simple — it’s just clear. But replicating it requires faking how light bends, how it casts subtle shadows, how it distorts the background, and how it adds depth even while being invisible.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/322/1*XFY1eI6vFhc2iA3v8fPmtw.png" /><figcaption>Example from Apple’s iOS 26 liquid glass design</figcaption></figure><p>We are going to try to replicate, or get close to it. To achieve it, in this shader we’ll simulate:</p><ul><li><strong>Refraction —</strong> light bending through a lens</li><li><strong>Magnification Falloff</strong> — a center-focused lens effect</li><li><strong>Shadow &amp; Occlusion</strong> — grounding the object in space</li><li><strong>Edge Lighting</strong> — simulating the thickness of the material</li></ul><p>Let’s walk through each part.</p><h3>Refraction &amp; Magnification— The Heart of the Illusion</h3><p>Refraction is what gives glass its identity. When light passes from one medium (air) to another (glass), it bends — this is called Snell’s Law. In shaders, we fake that by offsetting the sample location of the background image.</p><p>We’ll render a grayscale preview of the distortion intensity using a parabolic falloff (1 — r²). This simple shader helps to visualize it.</p><pre>[[stitchable]] half4 refractionVisual(<br>    float2 position,<br>    SwiftUI::Layer layer,<br>    float2 size,<br>    float2 glassCenter,<br>    float glassRadius,<br>    float refraction,<br>) {<br>    float2 uv = position / size;<br>    float2 toCenter = uv - glassCenter;<br>    float dist = length(toCenter);<br>    float normalizedDist = dist / glassRadius;<br><br>    // Default to background layer<br>    half4 originalColor = layer.sample(position);<br>    <br>    // Outside the glass: return original<br>    if (normalizedDist &gt; 1.0) {<br>        return originalColor;<br>    }<br><br>    // Compute distortion strength<br>    float distortion = 1.0 - normalizedDist * normalizedDist * refraction;<br><br>    // Visualize it: brighter = more distortion<br>    return half4(distortion, distortion, distortion, 1.0);<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*VF09jxd64gYxT3rB5ZBgDQ.gif" /></figure><p>This visualizes how strongly each pixel would be distorted if we applied refraction.</p><ul><li><strong>White</strong> = maximum distortion (center)</li><li><strong>Black</strong> = zero distortion (edge and outside)</li></ul><p><em>Note that, to be able to sample a certain location, we’ll use the SwiftUI modifier .layerEffect()</em></p><p>To visualize the result, we just need to sample the shifted position.</p><pre>    float distortion = 1.0 - normalizedDist * normalizedDist;<br>    float2 offset = toCenter * distortion * refraction;<br><br>    return layer.sample(position + offset);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*QCtAYm1FQtwV7RGKWUMJqA.gif" /></figure><p>As discussed above, we are using a parabolic falloff “<em>(1 — r²)</em>”, but we could use other curves like “<em>1.0 — pow(normalizedDist, 8.0)”</em> or even “<em>sin(normalizedDist * π)”</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*o-eicu3Vbv24hLfIhPA8WQ.gif" /><figcaption>Effect using `1.0 — pow(normalizedDist, 8.0)`</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*5T3kp6lKEzWFLVwuSBYg3A.gif" /><figcaption>Effect using `sin(normalizedDist * π)`</figcaption></figure><p>We can even use this same concept to create a magnifying glass that applies full distortion along all the surface and has a tight edge with something like this “<em>float falloff = clamp(distortionAmount * 0.5, 0.0, 0.1)</em>”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*nOGkqO8OHEyGXRpHaFELPg.gif" /><figcaption>Effect when clamping values</figcaption></figure><p><a href="https://x.com/victorbaro/status/1939020129586839764?s=61">I am exploring a combination of these concepts</a> to create a “liquid glass” magnifying glass for Panels.</p><h3>Adding Shadow &amp; Occlusion</h3><p>At this point, our glass object bends light convincingly — but it still looks like it’s floating on top of the scene. Now it needs to interact with the light around it. Real transparent objects, like marbles or lenses, don’t just bend light — they also cast subtle shadows. Even a drop of water has a small occlusion halo.</p><p>To simulate this, we’ll add a <strong>soft, offset shadow</strong> just outside the glass radius. This gives depth and spatial context.</p><ul><li>The shadow is <strong>not centered</strong> — we offset it slightly downward and to the right to simulate a directional light source.</li><li>It only appears <strong>outside the glass</strong> but within a <strong>blur ring</strong>.</li><li>We use smoothstep() to create a feathered falloff.</li></ul><pre>float2 shadowCenter = glassCenter + float2(shadowOffset, shadowOffset);<br>float2 toShadowCenter = uv - shadowCenter;<br>float shadowDist = length(toShadowCenter);<br><br>float shadowRadius = glassRadius + shadowBlur;<br>bool insideGlass = (normalizedDist &lt;= 1.0);<br>bool insideShadow = (shadowDist &lt; shadowRadius);<br><br>half4 result = layer.sample(position);<br><br>if (!insideGlass &amp;&amp; insideShadow) {<br>    float shadowFalloff = (shadowDist - glassRadius) / shadowBlur;<br>    float shadowStrength = smoothstep(1.0, 0.0, shadowFalloff);<br>    result = mix(result, half4(0.0, 0.0, 0.0, 1.0), shadowStrength * 0.05);<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/952/1*yCf0b4xlXJ1lFfvAIA2p6g.png" /></figure><p>Something important to note here is that your maxSampleOffset might need to be adjusted (depending on your blurRadius). If you use a .zero offset (or something smaller than your shadow) you will see the shadow cut off.</p><pre>content.<br>  layerEffect(ShaderLibrary.glassEffect(…), <br>              maxSampleOffset: .init(width: 150, height: 150)<br>)</pre><h3>Simulating Edge Lighting (Rim Highlight)</h3><p>Even though glass is transparent, the way it <strong>catches light at the edges</strong> gives it a sense of <strong>form and thickness</strong>.</p><p>You can see this clearly in the top reference image: a bright rim wraps around the top-left portion of the glass, suggesting curvature.</p><p>We’ll simulate this using a <strong>rim light effect</strong> — a glow that fades in at the very edge of the circle, based on distance to the glass boundary.</p><p>This is a simple implementation that creates a ring light:</p><pre>float edgeThickness = 0.015;<br>        float edgeDistance = abs(dist - glassRadius);<br>        float edgeFade = smoothstep(edgeThickness, 0.0, edgeDistance);<br><br>        half3 highlightColor = half3(1.1, 1.1, 1.2); // Cool-toned highlight<br>        result.rgb += edgeFade * highlightColor;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/964/1*N7qEK-RlXzsy35r4ar773g.png" /></figure><p>Almost there, now we just need for the highlight to appear at the top left. We can achieve that by modulating the rim strength with a fake light direction.</p><pre>float edgeThickness = 0.015;<br>float edgeDistance = abs(dist - glassRadius);<br>float edgeFade = smoothstep(edgeThickness, 0.0, edgeDistance);<br><br>// Add directional lighting<br>float2 lightDir = normalize(float2(-0.5, -0.8)); // upper-left<br>float rimBias = dot(normalize(toCenter), lightDir);<br>rimBias = clamp(rimBias, 0.0, 1.0);<br><br>// Modulate edge brightness by light direction<br>half3 highlightColor = half3(1.1, 1.1, 1.2);<br>result.rgb += edgeFade * rimBias * highlightColor;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/956/1*q27c2NLkt2lzj7ic1417WQ.png" /></figure><h3>Bonus: Chromatic aberration — Imperfection as Detail</h3><p>Our glass already refracts, casts shadows, and catches light. But in real life, even high-quality lenses aren’t perfect. At the edges, they often <strong>split white light into its component colors</strong> — a subtle rainbow fringe. This is called <strong>chromatic aberration</strong>.</p><p>We can simulate it by offsetting each color channel slightly differently when we sample the background. Red shifts one way, blue shifts the other. Green stays put.</p><pre>// Chromatic aberration strength increases toward the edge<br>float chromaticStrength = normalizedDist * 0.1;<br><br>float2 redOffset = refractedOffset * (1.0 + chromaticStrength);<br>float2 blueOffset = refractedOffset * (1.0 - chromaticStrength);<br><br>// Sample each channel separately<br>float2 redPos = position - redOffset * size;<br>float2 bluePos = position - blueOffset * size;<br><br>half4 redSample = layer.sample(redPos);<br>half4 blueSample = layer.sample(bluePos);<br><br>// Assign RGB channels separately<br>refractedColor.r = redSample.r;<br>refractedColor.b = blueSample.b;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/958/1*bElhaoWXcV_80A6GZTSOOw.png" /></figure><p>Previous examples used black text — black is the absence of color (0,0,0) so there is no color shift we can do to produce a chromatic aberration. We could potentially add it manually, but for simplicity I decided to use white text.</p><h3>Wrapping Up</h3><p>We started with a simple falloff. Then layered in:</p><ul><li>Directional refraction</li><li>Soft shadows</li><li>Edge lighting</li><li>Chromatic aberration</li></ul><p>Each step added one piece of physical truth. Combined, they create a shader that <em>feels</em> like glass — even though it’s just a function and a few uniforms.</p><p>The full working shader is available in the bonus content of the <a href="https://metal.graphics">Metal for SwiftUI course</a>, along with dozens of other examples like holographic stickers, neon signs, and animated waveforms.</p><p>Want to build your own custom shaders? The course is completely free, I hope you like it.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3f97974fbc24" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Prototyping with code]]></title>
            <link>https://medium.com/produkt-blog/prototyping-with-code-a0d2fe4ddb4f?source=rss-11fcfb2d5448------2</link>
            <guid isPermaLink="false">https://medium.com/p/a0d2fe4ddb4f</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[comics]]></category>
            <category><![CDATA[prototype]]></category>
            <category><![CDATA[design]]></category>
            <dc:creator><![CDATA[Victor Baro]]></dc:creator>
            <pubDate>Mon, 03 Sep 2018 16:39:23 GMT</pubDate>
            <atom:updated>2018-09-03T16:39:23.727Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*746qB9xxU1MHuRJ6b8KqZg.jpeg" /></figure><p>Last week we released a new version of <a href="https://www.google.com/url?sa=t&amp;rct=j&amp;q=&amp;esrc=s&amp;source=web&amp;cd=1&amp;cad=rja&amp;uact=8&amp;ved=2ahUKEwikneG-y5jdAhUHFnwKHToDCBEQFjAAegQICRAB&amp;url=https%3A%2F%2Fitunes.apple.com%2Fus%2Fapp%2Fpanels-comic-reader%2Fid1236567663%3Fmt%3D8&amp;usg=AOvVaw0icWll4K1V2GbZn1SX8ouW"><strong>Panels</strong></a>, our comic reader for iOS. In this version we introduced a new feature that we like to call <strong>Zoom control, </strong>which allows you to zoom and move along one page with one finger after long pressing a page.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FfIomRhAHlKQ%3Ffeature%3Doembed&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DfIomRhAHlKQ&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FfIomRhAHlKQ%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/25e398ebe1b9c999c105ad6d8c15e635/href">https://medium.com/media/25e398ebe1b9c999c105ad6d8c15e635/href</a></iframe><p>The idea behind this feature was to improve the reading experience for those users reading comics on iPhone (portrait orientation). In previous versions, to read comics while in portrait mode you’d have to pinch the screen to zoom in and out using two hands, which makes the reading experience tedious and time consuming.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FwfC5HPv58tc%3Ffeature%3Doembed&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DwfC5HPv58tc&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FwfC5HPv58tc%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/73d8fb011d0895c4f486cfec8a213ae9/href">https://medium.com/media/73d8fb011d0895c4f486cfec8a213ae9/href</a></iframe><p>We still think that panel-by-panel navigation is the ideal solution, but it is challenging to implement. This is why we decided to prototype other ideas while we keep working on a panel-by-panel solution.</p><p>Prototyping can be a long process. Especially materialising different ideas into something that you can test and feel. This is why solutions like <a href="https://marvelapp.com">Marvel</a> or <a href="https://framer.com">Framer</a>, to name a few, are so good. With nearly 0 effort (and 0 code) you can get your idea ready to test.</p><p>But, as developers, we like to code. To prove a hypothesis, to check how a new interaction feels, it is easier for us to build it on top of what we already have.</p><h4>Solution 1: magnifying glass</h4><p>Our first approach was to build a magnifying glass (similar to the built-in iOS that appears after long pressing on text).</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FMppiS0iD3qY%3Ffeature%3Doembed&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DMppiS0iD3qY&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FMppiS0iD3qY%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/3529154ab3de1e4c2d404c9255b6e17a/href">https://medium.com/media/3529154ab3de1e4c2d404c9255b6e17a/href</a></iframe><p>To create a magnifying glass view on iOS you can simply instantiate a view that renders scaled (or zoomed) content from another view.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0458e4da3d2dc2d88c67224c319fc358/href">https://medium.com/media/0458e4da3d2dc2d88c67224c319fc358/href</a></iframe><p>On our heads, this solution was pretty good, but the main problem we noticed as soon as we tested it on a real device was how uncomfortable it was. Having to drag your finger along the screen was incredibly annoying and, after reading a few pages, the user could get frustrated.</p><p>However, the main issue was the fact that you need to reach with your finger all four corners of the device.</p><h4>Solution 2: magnifying glass + better control</h4><p>Suddenly I remembered a “hidden” feature of iOS -&gt; force touch the keyboard to move the caret position.</p><p>The idea of dragging your finger across the screen was bad, but if there was a small area where the user could control the magnifying glass, with shorter movements, it would solve the problem.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FsZbYLko4rno%3Ffeature%3Doembed&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DsZbYLko4rno&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FsZbYLko4rno%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/c0c9df712634d133cbcf1ad1612d4800/href">https://medium.com/media/c0c9df712634d133cbcf1ad1612d4800/href</a></iframe><p>Technically, this iteration was simple to implement. The long gesture recognizer is applied to the small blue view (top bottom). This view is <strong>XX</strong> times smaller than the page. To calculate the position of the magnifying view, we only need to know the position of the finger in the blue control view, and multiply both x and y values by <strong>XX</strong>.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4713427a05f2ce6c8fb488bedcc9cad4/href">https://medium.com/media/4713427a05f2ce6c8fb488bedcc9cad4/href</a></iframe><p>This solution was much better but, after playing with it for a while, we realised that not all speech bubbles will fit in the magnifying glass, which makes it difficult to read.</p><p>In addition, there was too much focus on the magnifying glass content, preventing the user to read the panel as a whole (text + image). We found ourselves activating and deactivating the magnifying glass all the time. To solve this problem we thought… “let’s make the magnifying glass big… bigger…”.</p><h4>Solution 3: zooming the whole image + better control</h4><p>We made the magnifying glass so gigantic that it almost filled the entire screed. We realised that, instead of using a magnifying glass, we could just zoom the whole content.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fh1j9CxFodDk%3Ffeature%3Doembed&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Dh1j9CxFodDk&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2Fh1j9CxFodDk%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/832a237b53b5f47221fe8ac3ea1e3e55/href">https://medium.com/media/832a237b53b5f47221fe8ac3ea1e3e55/href</a></iframe><p>This was a great improvement. The user was not restricted anymore by a small rounded area, the whole page was zoomed and it could be easily controlled from one corner, with just one finger.</p><p>There still were a few problems remaining. How are we going to train our users to long press the bottom-right corner of the screen? We couldn’t leave a red square to showcase the long-press area. And how about the left-handed users?</p><h4>Solution 4: Final feature</h4><p>The final solution was to instantiate a control view (red view on the video below) when the long press gesture state was recognized. That way, it doesn’t really matter where the user puts his/her finger, it will always work.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F2PU21dzJ5Qo%3Ffeature%3Doembed&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D2PU21dzJ5Qo&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F2PU21dzJ5Qo%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/b56893bc4df8572a5a9b782c9c50dfe4/href">https://medium.com/media/b56893bc4df8572a5a9b782c9c50dfe4/href</a></iframe><h4>Wrapping up!</h4><p>As developers, we can also (and should) prototype and try new ideas and interactions. We can do it very quickly (specially with Playgrounds!), it doesn’t need to be production code, and surely we don’t need any external tools. Sometimes is good to start coding even when you don’t know where you will end up.</p><p>For this particular case, this feature turned up to be a bit “scary”, because we were unsure how the users would react. We were sceptic the first time we tried it, but it took us just a few seconds to realise it was actually helpful and felt very natural.</p><p>And soon after shipping, users started to love it too ❤️</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*n6u9-n8gWkj0urn9qUAlDA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*p5VOJfH4HS2dUgeL2sEEfg.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a0d2fe4ddb4f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/produkt-blog/prototyping-with-code-a0d2fe4ddb4f">Prototyping with code</a> was originally published in <a href="https://medium.com/produkt-blog">Produkt Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[VisualKit: UI Framework]]></title>
            <link>https://medium.com/jobandtalenteng/visualkit-ui-framework-74ab8aae0d42?source=rss-11fcfb2d5448------2</link>
            <guid isPermaLink="false">https://medium.com/p/74ab8aae0d42</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[mobile]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[design]]></category>
            <category><![CDATA[atomic-design]]></category>
            <dc:creator><![CDATA[Victor Baro]]></dc:creator>
            <pubDate>Mon, 10 Apr 2017 13:32:29 GMT</pubDate>
            <atom:updated>2019-05-07T13:38:11.449Z</atom:updated>
            <content:encoded><![CDATA[<h3>VisualKit: creating a UI Framework</h3><p><em>This post covers how the iOS team has created and dealt with a UI Framework, its structure and relation with the main project, as well as the benefits and downsides of this decision. Don’t miss the </em><a href="https://jobandtalent.engineering/learning-to-develop-jobandtalents-design-system-for-android-54160a571d7b"><em>post on the same topic from the android team!</em></a></p><p>About a year ago, the mobile team at Jobandtalent was presented with a huge project. This new project had many new features, screens, interactions… <a href="https://jobandtalent.design/holistic-design-for-companies-and-candidates-35c94585f84a">The design team took this opportunity to improve their workflow by introducing atomic design</a>, which defines a <em>blueprint</em>, with buttons, controls, cells, etc. That is, to create new screens they only used existing components and combinations of them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*i1H2mYXq_v1tSs587n2zwg.gif" /><figcaption>Atomic design example</figcaption></figure><p>Based on this concept, the design team at Jobadntalent created their own design system.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*r7SFMmsgdAmgeIamw1pGxw@2x.jpeg" /><figcaption>Jobandtalent design system example</figcaption></figure><p>We applied the atomic design philosophy in our project by creating new standard components and using them in multiple scenarios. This approach allowed us to write <strong>reusable</strong> UI code.</p><p>VisualKit is Jobandtalent’s UI Framework. In a nutshell, <strong>VisualKit is a big modular package of views and controls where each one of them can be easily themed and modified to fit the design, just like playing with Lego.</strong></p><p><a href="https://jobandtalent.engineering/ios-architecture-separating-logic-from-effects-7629cb763352">iOS Architecture: Separating logic from effects</a></p><h4>VisualKit Structure</h4><p>VisualKit contains views and controls, including blank slates, buttons, loaders, segmented controls, switches, cells, etc. All new visual components are created in VisualKit.</p><p>Let’s have a look at an example on how we break down a screen into different components.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/904/1*nuZp0OoqTrpnv2uPMI5CFQ.png" /></figure><p>Forget about the navigation and tab bars, and let’s focus on the main tableView.</p><p>Each cell has three different <em>sections</em>:</p><ul><li>Header: contains the avatar, labels, a disclosure and a red dot (optional).</li><li>Special label: we call it ChipView and indicates state. It can be red, green or yellow.</li><li>Information breakdown: contains labels displayed in two columns and multiple rows.</li></ul><p>VisualKit declares any view needed from the main project as public.</p><p>In the previous case, each individual <em>section</em>, as well as the cell (which is a combination of the 3 sections) will be declared public. This allow us to <strong>reuse</strong> each section throughout the main project. As shown below, the same cell is used multiple times.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*g4vrEyYU95AjDtTETknJnQ.png" /></figure><p>If we make the cell modular, <strong>we only need to code it once</strong>. We simply need to:</p><p>1. Create each individual component (avatar, label, disclosure..)</p><p>2. Make the container view capable of rendering itself in different configurations by combining its individual components. <strong>StackViews</strong> are a great tool for this modular approach.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cYPh0UBS2bs4J5JUJa1PTQ.png" /></figure><p>For example, to create the header, we use a horizontal StackView that contains the red indicator, the avatar, another vertical StackView and the right disclosure. The vertical StackView contains the two main labels.</p><p>VisualKit makes the header publicly available and configurable from the main project.</p><p>The API for components like the header is very simple and resembles UIKit standard components.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/47886674035b1c782734d84dfa48f470/href">https://medium.com/media/47886674035b1c782734d84dfa48f470/href</a></iframe><p>This view is not only ready to be used from the main project, but also from within VisualKit itself to generate more complex views.</p><p>There is only one particular property, though, it is worth to pay attention to: style.</p><h4>Styles: Conforming to Styleable protocol</h4><p>In VisualKit each view conforms to the following protocol:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/70f5f2861335544a8c5c8eb0d7ac3e23/href">https://medium.com/media/70f5f2861335544a8c5c8eb0d7ac3e23/href</a></iframe><p>It is a simple yet powerful protocol that makes all components behave equally. Furthermore, each component must define its own styles.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/226/1*x7o27z4DNCkn-USl145RHA.png" /></figure><p>For example, our ChipView has three different styles. To make the ChipView conform to our Styleable protocol, we first need to define each style.</p><p>We do so by creating a public struct, where we define each aspect to be customised by each style. This same struct will also have different class initializers for the different styles.</p><p>The struct initialiser is not publicly available. This restricts anyone from the outside to create and set new styles.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/369017c40f892800f43a4bcd340d715c/href">https://medium.com/media/369017c40f892800f43a4bcd340d715c/href</a></iframe><p>Last, we can extend our chipView to conform to our protocol (<em>Note how we specify the type, in this case, </em><em>ChipStyle</em>):</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fbf1f1bb378b8664a9e0268642340bfa/href">https://medium.com/media/fbf1f1bb378b8664a9e0268642340bfa/href</a></iframe><p>This is useful, particularly when all your components conform to the same protocol. Now, from the main project, we can simply do:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e8ad5a525da45270eddd6359837ac884/href">https://medium.com/media/e8ad5a525da45270eddd6359837ac884/href</a></iframe><p>And we are done.</p><p>Create each component to adapt itself to change between different styles is hard work, but it pays off. Once is set up, it is incredibly easy to create new styles.</p><p>The same applies for buttons, cells, and may many other components. It is also useful for UIKit classes like UILabel. Here is an example:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6b487bd4fba183877f1182fd575b7f7d/href">https://medium.com/media/6b487bd4fba183877f1182fd575b7f7d/href</a></iframe><p><strong>Styles: Colors and Fonts</strong></p><p>We created two abstractions over UIColor and UIFont in Visualkit, ColorPalette and FontBook. They are simple but they get the job done.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*guwIuC2IQsxi0sSiJ2R64w.png" /><figcaption>ColorPalette and FontBook</figcaption></figure><p>Let’s start with FontBook. This class manages the full set of fonts available in the Jobandtalent app. We only use the fonts designers have previously defined. Nevertheless, there is a public function that takes a name and a size and returns the correct Font.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3b149f9387240cdfad47f421b57b9337/href">https://medium.com/media/3b149f9387240cdfad47f421b57b9337/href</a></iframe><p>To use it, for example, we call FontBook.extralarge</p><p>The ColorPalette is, again, a series of defined colors. The problem with colors is the alpha value. Even if your designer uses UIColor.blue there is a wide range of colors that they can choose just by playing with the opacity. This is why all our colors only have seven alpha possibilities + the base color (alpha = 1).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/95de2d97a5674e52ffcb53a2d34a7cec/href">https://medium.com/media/95de2d97a5674e52ffcb53a2d34a7cec/href</a></iframe><p>In the project, we only use colors defined inside the ColorPalette like this: view.backgroundColor = ColorPalette.dark.alpha80</p><h3>Project example</h3><p>One of the nicest “side effects” of having a separate UI Framework is to reuse it in different projects. We created a new specific project to play with VisualKit.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/370/1*h4MK6BPeYW6XH2sVBvjp8A.gif" /></figure><p>The example app serves many different purposes:</p><ul><li>To develop our controls outside the main project. VisualKit is all about UI: it is quicker to test controls and see them in action without having to navigate to a specific screen buried in the app. Playgrounds are very useful for that.</li><li>To help the design team validate new components. The app includes <a href="https://github.com/Flipboard/FLEX">Flex</a>, which is awesome for tweaking values in realtime.</li><li>To be used as a “library”. Once the framework started to grow, we needed a quick visual reference to each individual component.</li></ul><h3>Creating and Working with a framework</h3><p>Our project already managed dependencies using <a href="https://cocoapods.org">Cocoapods</a>. Once we decided to create this framework, it made sense to take advantage and develop it as a private pod (check this <a href="https://guides.cocoapods.org/making/private-cocoapods.html">brilliant guide</a> for more details on how to set it up).</p><p>I would like to give you my 2 cents on how easy/hard quick/slow things can become when you move most of your UI classes to an external source:</p><ul><li>Cocoapods is a great tool. For instance, when using developments pods, you are able to modify them within your project. However, every time you create a new file, you need to run pod install and obviously let your team know about it. We do not have it versioned as it is not mature enough, but it is definitely something to consider.</li><li>Public, internal, open and private become more powerful than ever. When working on a project, you hardly ever use public. By default (internal) all files can see each other. When you encapsulate your classes in a framework, only those (classes, functions, protocols) marked as public will be available. This simple fact makes you more aware when designing your classes’ APIs and gives you more power (now you have a three layer access).</li><li>You need to import VisualKit in (mostly) every view controller. We could use the .pch but VisualKit should only be available from the UI Layer. Other internal layers do not need to know or deal with it.</li></ul><h3>“Opensourcing” VisualKit</h3><p>Opensourcing VisualKit as it is today would be useless: it is coupled to our app’s design.</p><p>However, everytime we create a new component we make it as an isolated instance. From VisualKit, we have already released two components: <a href="https://github.com/jobandtalent/AnimatedTextInput">AnimatedTextInput</a> and <a href="https://github.com/jobandtalent/CardStackController">CardStackController</a>. We will release new components as soon as they are ready.</p><p>Everyone on the iOS team agrees that having VisualKit as a framework was a great decision. It made us work faster and respond quicker to design changes.</p><h3>Designers and developers working together</h3><p>We could argue about the usefulness of having these abstractions over colors and fonts, or having styles for each component. Is this worth it? Can’t I just go ahead and do that on the storyboard/xib or in code every time I create a new view?</p><p>Obviously you can. But we should be able to respond fast to any design change. Maybe designers want a brighter blue, a new font or a faster animation. It is difficult to foresee what will change in the future, but working alongside designers can help you and save you time.</p><p>Our solution tries to mimic our designers approach. We create screens in a very similar way as they do. If they update the color palette, we can update it too and everything will “react” to this change. It shouldn’t be a hassle for developers to introduce a new font across the app.</p><p>Working alongside designers helped us understand their design process. In turn, designers also learnt from us, finding new solutions to overcome technical restrictions.</p><p>As a final note, it might not be a developer’s job to be part of early design decisions. It is usually managed by the project managers and designers. However, during this time I have experienced that being part of that process as early as possible, will really help your team.</p><p><em>Thanks to </em><a href="https://twitter.com/luisrecuenco"><em>Luis Recuenco</em></a><em>, </em><a href="https://twitter.com/fillito"><em>Daniel García</em></a><em>, </em><a href="https://twitter.com/karpov"><em>Rubén Mendez</em></a><em>, </em><a href="https://twitter.com/rais38"><em>Rafa Aguilar</em></a><em>, </em><a href="https://twitter.com/xavierjurado"><em>Xavier Jurado</em></a><em>, </em><a href="https://twitter.com/poolqf"><em>Pol Quintana</em></a><em>, </em><a href="https://twitter.com/dmartincy"><em>Daniel Martín</em></a><em>, </em><a href="https://twitter.com/alexissan"><em>Alexis Santos</em></a><em>, </em><a href="https://twitter.com/saky"><em>Isaac Roldán</em></a><em>, </em><a href="https://twitter.com/andrewsbrun"><em>Andrés Brun</em></a><em> and </em><a href="https://twitter.com/monchote"><em>Ramón Argüello</em></a><em> for contributing and making this possible.</em></p><p>— — — — — — — — — — — — — — — — — — — — — — — — —</p><h3>Bonus: Playgrounds!</h3><p>I love playgrounds, and <em>live updates</em> give us superpowers. I use them all the time when creating a new component for VisualKit.</p><p>Here is a simple template I use to start a new Playground.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d9a0ce20af10ba927df3420c4a02d2c1/href">https://medium.com/media/d9a0ce20af10ba927df3420c4a02d2c1/href</a></iframe><p>I have speed-recorded myself creating a new component. <a href="https://gist.github.com/victorBaro/067f81d5a1b0b1f239833dfd17ba4bd8">You can find it here</a>. I believe it is a good example of a VisualKit component (atom), that we have reused multiple times as part of other views (molecules).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/1*Xqr74olhlTijTlA_-uO3Aw.gif" /></figure><p><a href="https://jobandtalent.engineering/https-medium-com-aracem-working-with-a-design-system-f426be09c470">Working with a design system</a></p><h4>We are hiring!</h4><iframe src="https://cdn.embedly.com/widgets/media.html?type=text%2Fhtml&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;schema=twitter&amp;url=https%3A//twitter.com/jobandtalenteng/status/1125743791196049409&amp;image=" width="500" height="281" frameborder="0" scrolling="no"><a href="https://medium.com/media/7171964a0e0f5cd8313526de1e33393f/href">https://medium.com/media/7171964a0e0f5cd8313526de1e33393f/href</a></iframe><p>If you want to know more about how is work at Jobandtalent you can read the first impressions of some of our teammates on this<a href="https://jobandtalent.engineering/our-first-impressions-working-at-jobandtalent-part-1-991a48eac2a4"> blog post</a> or visit our<a href="https://twitter.com/jobandtalentEng"> twitter</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=74ab8aae0d42" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jobandtalenteng/visualkit-ui-framework-74ab8aae0d42">VisualKit: UI Framework</a> was originally published in <a href="https://medium.com/jobandtalenteng">Job&amp;Talent Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[3D Force Touch: beyond peek & pop]]></title>
            <link>https://medium.com/produkt-blog/3d-force-touch-beyond-peek-pop-c448edc2b1f5?source=rss-11fcfb2d5448------2</link>
            <guid isPermaLink="false">https://medium.com/p/c448edc2b1f5</guid>
            <category><![CDATA[xcode]]></category>
            <category><![CDATA[interaction]]></category>
            <category><![CDATA[force-touch]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[ux]]></category>
            <dc:creator><![CDATA[Victor Baro]]></dc:creator>
            <pubDate>Mon, 19 Oct 2015 22:21:57 GMT</pubDate>
            <atom:updated>2016-10-19T02:07:50.892Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vokyY8Jtu-Z09qY888CSYw.jpeg" /></figure><p>A few days ago I bought an iPhone 6S. I was super impressed with its new <strong>3D touch</strong> and I could not wait to start experimenting.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fd-hlQISXj8M%3Ffeature%3Doembed&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Dd-hlQISXj8M&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2Fd-hlQISXj8M%2Fhqdefault.jpg&amp;key=d04bfffea46d4aeda930ec88cc64b87c&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/0391a31c4d0f9fa8b9677ea6e3cb52eb/href">https://medium.com/media/0391a31c4d0f9fa8b9677ea6e3cb52eb/href</a></iframe><p>Peek &amp; pop is a great feature to include in an app. The downside: we don’t have much control over it. We can only add a preview and a few actions — iOS manages the rest.</p><p>Since I discovered <em>3D Touch</em>, I have been thinking about new ways of interacting with the content. Peek &amp; pop is a great interaction; but what I really want is to create my own controls.</p><p>We need to take into account that, because <em>3D touch </em>is only available in iPhone 6S and 6S Plus, no action should be completed <strong>just</strong> by using this feature. The user should be able to achieve any action without using <em>3D touch</em> (just like peek &amp; pop does), and <em>3D touch </em>should only provide an extra level of interaction.</p><h4>Accessing force property</h4><p>The new force property can be found in UITouch class. To get the user <em>touch</em> we should override <em>touches</em> methods (touchesBegan, touchesMoved, touchesEnded), either subclassing (e.g. UIView, UIButton; see example 1) or creating a gesture recognizer (see below; used in examples 2 and 3).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5859e06730506828d4ed3e068d70baf7/href">https://medium.com/media/5859e06730506828d4ed3e068d70baf7/href</a></iframe><p>The force value goes from 0.0 to 6.667. For further details, I extremely recommend <a href="https://medium.com/@rknla/exploring-apple-s-3d-touch-f5980ef45af5">Exploring Apple’s 3D Touch post</a>.</p><h4>Example 1: Force Button</h4><p><strong>Force Button</strong> is a subclass of UIButton that modifies its shadow based on the input force (as seen in the top video).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3a2ebc051e5d01234913f755c549bc84/href">https://medium.com/media/3a2ebc051e5d01234913f755c549bc84/href</a></iframe><p>The above function modifies the button shadow based on input force. You can find another example on how to modify the button scale while its being pressed <a href="https://github.com/Produkt/3dForceTouchExamples">here</a>.</p><p>This button uses <em>3D touch</em> only for visual purposes, it does not add any extra feature. It might be nice to add an extra event (e.g. <em>UIControlEvents.ForceMaxInside) </em>to add a taget action once the user has pressed the button to its maximum force.</p><h4>Example 2: Zooming</h4><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F8RcDqH4kfo8%3Ffeature%3Doembed&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D8RcDqH4kfo8&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F8RcDqH4kfo8%2Fhqdefault.jpg&amp;key=d04bfffea46d4aeda930ec88cc64b87c&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/cf41d8a22d3a70cfc7c58d557730d7bc/href">https://medium.com/media/cf41d8a22d3a70cfc7c58d557730d7bc/href</a></iframe><p>We are all used to pinch to zoom in and out, it feels natural. However, sometimes it is tricky to use 2 fingers while holding the device with 1 hand. Google maps app tries to solve this issue by using a <em>doble-tap-longPress-drag</em> gesture (which feels weird if you are not use to it).</p><p>Using ForceGestureRecognizer (see code above), it is really easy to zoom in and out while dragging your finger. If you have an iPhone 6S give it a go, it feels great.</p><p>To achieve this effect, I simply apply a CATransform3D scale to the imageView layer. By doing this, the image scales from its center. To move the image under my finger (zooming to an specific area) I need to update the anchorPoint based on the finger’s location.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/758d45501850cf3fb060b9c1b09c7b10/href">https://medium.com/media/758d45501850cf3fb060b9c1b09c7b10/href</a></iframe><h4>Example 3: Controlling animations</h4><p>The last interaction I am proposing for the 3D<em> touch</em> is to <strong>control an animation</strong>. To be honest, I haven’t found any interesting use for this interaction (other than being great for fine-tunning), but I would like to mention it (someone might find it useful).</p><p>Here is a quick video of an animation being controlled with <em>3D touch.</em></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FLXQ-iSYhHFI%3Ffeature%3Doembed&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DLXQ-iSYhHFI&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FLXQ-iSYhHFI%2Fhqdefault.jpg&amp;key=d04bfffea46d4aeda930ec88cc64b87c&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/fc02b926d84ad781607f153402a32029/href">https://medium.com/media/fc02b926d84ad781607f153402a32029/href</a></iframe><p>These are just a few examples of new ways of interaction that <em>3D Touch</em> brings to designers and developers. I hope I have convinced you to try <em>3D Touch</em>.</p><p>I would like to finish by recommending <a href="http://flexmonkey.blogspot.com.es">FlexMonkey’s bog</a>, and specially his latest post: <a href="http://flexmonkey.blogspot.com.es/2015/10/3d-retouch-experimental-retouching-app.html">3D Retouch</a>, where he uses 3D Touch to modify the intensity of filters.</p><p>Find the whole project in <a href="https://github.com/Produkt/3dForceTouchExamples">github</a>.</p><p><em>Special thanks to @pivalue</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c448edc2b1f5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/produkt-blog/3d-force-touch-beyond-peek-pop-c448edc2b1f5">3D Force Touch: beyond peek &amp; pop</a> was originally published in <a href="https://medium.com/produkt-blog">Produkt Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Recreating Apple’s Rubber Band Effect in Swift]]></title>
            <link>https://medium.com/@victorbaro/recreating-apple-s-rubber-band-effect-in-swift-dbf981b40f35?source=rss-11fcfb2d5448------2</link>
            <guid isPermaLink="false">https://medium.com/p/dbf981b40f35</guid>
            <category><![CDATA[interaction]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Victor Baro]]></dc:creator>
            <pubDate>Fri, 08 May 2015 09:01:07 GMT</pubDate>
            <atom:updated>2015-05-08T09:01:07.953Z</atom:updated>
            <content:encoded><![CDATA[<p>For the past 6 months we have been working on the most challenging and fun app I have ever developed: <a href="http://thoughts.ink"><strong>Thoughts</strong></a>. This is also the first <a href="http://www.produktstudio.com/">Produkt’s</a> own app, so we couldn’t be more excited about its imminent launch.</p><p>One of the trickiest features we encountered while developing Thoughts was to recreate Apple’s scrolling view capabilities (some of them).</p><p>Whilst Panning and Zooming can be defined by using gesture recognizers, combining the two was far more complicated. Zooming into a specific point (e.g. middle of your fingers) involves not only zooming but shifting the content. Nevertheless, my favourite part of all was to replicate the <strong>rubber band</strong> behaviour.</p><iframe src="https://cdn.embedly.com/widgets/media.html?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DiiXpLaZiTrs&amp;src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FiiXpLaZiTrs%3Ffeature%3Doembed&amp;type=text%2Fhtml&amp;key=d04bfffea46d4aeda930ec88cc64b87c&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/54401baad25c6bace4863e2c2d5d4277/href">https://medium.com/media/54401baad25c6bace4863e2c2d5d4277/href</a></iframe><h3>The maths behind it</h3><p>The Control Centre is a perfect rubber band interaction example.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/375/1*8hSXw6y_0h6GL4OTdJd-9Q.gif" /><figcaption>Control Center adds rubber band behaviour when it is pulled up</figcaption></figure><p>By interacting with the Control Centre behaviour, the first mathematical function that came to my mind was the square root. However… it didn’t feel right, and not as natural as Apple’s. Here is when Swift (and specially playgrounds) became very useful.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*4AB84Lyd3QLUdyGLMMXzjQ.png" /><figcaption>Swift playground for finding the most appropiate mathematical function</figcaption></figure><p>After a few tests, the best strategy was to use <em>log10</em> — it mimics quite closely Apple’s built-in behaviour.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tsqz8rPGUUjqNk_1s0cp1A.jpeg" /><figcaption>Logarithmic functions (screenshot from Tydling app)</figcaption></figure><p>I tried multiple logarithmic functions, including the neperian, but <em>log10</em> was the most optimal.</p><p>The GIF below illustrates the comparison between square root, <em>log10</em> and a function involving a power of 4. This last function works very well and, depending on the exponent, the ‘rubber’ effect becomes more/less elastic (that is, the distance gets longer/shorter). However, the downside of using this function is that there is a maximum value you shouldn’t cross (otherwise, the movement gets reversed).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/372/1*b0RundYA9bhE91RIXqYJLA.gif" /></figure><h3>Using this behaviour in ‘real life’ interactions</h3><p>Replicating Apple’s scroll view will not be necessary on a standard app; we did it for Thoughts to create its infinite canvas. However, some situations (like the example below) can require a rubber band effect where there is no scroll view involved.</p><p>To showcase one of the above functions, I have included the following example in the project.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/372/1*sDy-cSxRYvZ9DuZmYcjjvw.gif" /></figure><p>The green view is located using Autolayout and has a PanGestureRecognizer attached to it that calls the following method:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7705fd28cf72c1b7d2014f349d74f014/href">https://medium.com/media/7705fd28cf72c1b7d2014f349d74f014/href</a></iframe><p>Where the last part of it, the <em>logConstraintValueForYPoisition, </em>takes care of applying the rubber band effect after the view exceeds the vertical limit using a <em>log10</em> function. <em>Tip: I like to use log10 from 1 to X (0.x values grow massively quick).</em></p><p>In this example, <em>animateViewBackToLimit()</em> is implemented using a simple UIView animation block to keep the project simple, however <a href="https://medium.com/@flyosity/your-spring-animations-are-bad-and-it-s-probably-apple-s-fault-784932e51733">I am not a big fan of them</a>.</p><p><a href="https://github.com/Produkt/RubberBandEffect"><strong>Find the project in GitHub</strong></a></p><h4>One last word</h4><p>UIAttachmentBehaviour can also be used in some situations involving rubber band behaviour. This is only another tool to add in your toolbox ☺</p><p>PS: this is the first time I have published Swift code. I would really appreciate if you can help me to improve it in any way.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dbf981b40f35" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>