Create a ray scanning effect with SwiftUI and metal shader
Tags: swift, webdev
Published: 2023-11-29 03:44:11

I learned swiftui last month and built this ray scanning effect! I originally tweeted about the project here! And added the x-ray mode a week after!

{% embed https://twitter.com/BennyKokMusic/status/1715773209268899920 %}

I did this with the .colorEffect + custom shader that draws the grid line and clipped by the two circles at different times!

Below is a preview of the result.

![xray](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ongpi5qoc50fwo2rtx2a.gif)

`ImpactRevealOverlay` is a view modifier helper to integrate the effects and a `TimelineView` to dynamically modify the shader params. 


```swift
struct ImpactRevealOverlay: ViewModifier {
    
    var effectPlaying: Bool
    @State var startDate = Date()
    
    func body(content: Content) -> some View {
        content.overlay(content: {
            if #available(macOS 14.0, *) {
                if effectPlaying {
                    TimelineView(.animation) { context in
                        RoundedRectangle(cornerSize: CGSize(width: 10, height: 10))
                            .colorEffect(
                                ShaderLibrary.default.impactReveal(.boundingRect,
                                    .float(context.date.timeIntervalSince1970
                                           - self.startDate.timeIntervalSince1970))
                            ).blendMode(.overlay)
                            .opacity(0.1)
                    }
                }
            }
        }).onChange(
            of: effectPlaying
        ) { newValue in
            if effectPlaying {
                DispatchQueue.main.async {
                    startDate = Date()
                }
            }
        }
    }
}

```

The actual shader code here, which you will need to create a metal file named `Shaders` in your xcode project.

```glsl
#include <metal_stdlib>
using namespace metal;

[[ stitchable ]]
half4 impactReveal
(
 float2 position,
 half4 color,
 float4 bounds,
 float secs
 )
{
    float cols = 20;
    float timeScale = 0.04;
    
    vector_float2 uv = position/bounds.zw;
 
    // Define the grid line thickness
    float lineThickness = 0.05;
    // Create the grid lines
    vector_float2 grid = step(lineThickness, fract(uv * bounds.zw / cols));
    // Combine the grid lines
    float combinedGrid = 1 - min(grid.x, grid.y);
    
    // Calculate the distance from the current pixel to the center of the circle
    vector_float2 scaled_uv = position / bounds.z;
    float distanceToCenter = length(scaled_uv - float2(0.5, 0));
    // Define the radius of the circle
    float radius = 1.3 * (sin(secs / 1.5));
    float radius2 = 1.3 * (sin((secs - 0.5) / 1.5));
    // Check if the current pixel is inside the circle
    bool isInsideCircle = distanceToCenter <= radius;
    bool isInsideCircle2 = distanceToCenter <= radius2;
    // Only draw the grid if it is inside the circle
    combinedGrid *= isInsideCircle;
    combinedGrid *= !isInsideCircle2;
    
    vector_float4 background = vector_float4(0, 0, 0, 0);
    vector_float4 circles = vector_float4(1, 1, 1, 1);
    
    vector_float4 mixedColor = mix(background, circles, combinedGrid);
    
    mixedColor.a = 0.1;
    
    bool isColor = combinedGrid >= 1;
    
    return isColor ? half4(mixedColor) : half4(0, 0, 0, 0);
}
```

If you are interested in what I'm building here, please check https://www.findmyports.com/! 

It's a menubar app helping you manage your running ports!

I built a lot of cool projects in public on twitter, follow me for more updates!

{% embed https://twitter.com/BennyKokMusic/status/1697938257412239702 %}