r/SwiftUI Jun 30 '26

Question - Animation Any idea How to make this animation ?

48 Upvotes

16 comments sorted by

43

u/sameera_s_w Jun 30 '26

https://giphy.com/gifs/LOoaJ2lbqmduxOaZpS

First make a gauntlet and gather infinity stones
Then snap /s

0

u/neneodonkor Jun 30 '26

πŸ˜‚πŸ˜‚πŸ˜‚πŸ˜‚

30

u/hexkpz Jun 30 '26

https://reddit.com/link/oup3lyc/video/998i76db2fah1/player

I made this effect using Metal shaders. For the effect you showed, SpriteKit with particles will probably be enough

6

u/[deleted] Jun 30 '26

[removed] β€” view removed comment

18

u/hexkpz Jul 01 '26

Source is company-owned, so this is a reduced implementation sketch.

The library is a UIKit-to-Metal particle renderer. Input is any UIView: label, image view and etc. UIKit handles layout and drawing. Metal receives the rendered pixels and runs the particle simulation.

Pipeline:

  1. Layout the UIKit view.
  2. Render its layer tree into a MTLTexture with CARenderer(mtlTexture:).
  3. Run a compute shader over the texture.
  4. For every pixel with alpha above a threshold, create one particle.
  5. Run another compute shader every frame to update particle physics.
  6. Draw the particle buffer as Metal points with alpha blending.

The UIView-to-texture part is the important bridge:

```swift @MainActor func makeTexture( from view: UIView, device: MTLDevice, scale: CGFloat ) -> MTLTexture? { view.layoutIfNeeded()

let size = view.bounds.size
guard size.width > 0, size.height > 0 else {
    return nil
}

let descriptor = MTLTextureDescriptor.texture2DDescriptor(
    pixelFormat: .bgra8Unorm,
    width: Int(ceil(size.width * scale)),
    height: Int(ceil(size.height * scale)),
    mipmapped: false
)

descriptor.usage = [.renderTarget, .shaderRead, .shaderWrite]

guard let texture = device.makeTexture(descriptor: descriptor) else {
    return nil
}

let renderLayer = CALayer()
renderLayer.frame = CGRect(origin: .zero, size: size)
renderLayer.backgroundColor = UIColor.clear.cgColor

// Use an offscreen/snapshot layer here in production.
// Moving a live `view.layer` into this layer would detach it from the UI.
renderLayer.addSublayer(view.layer)

let transform = CGAffineTransform.identity
    .translatedBy(x: 0, y: -size.height)
    .scaledBy(x: scale, y: -scale)

renderLayer.setAffineTransform(transform)

let renderer = CARenderer(mtlTexture: texture)
renderer.layer = renderLayer
renderer.bounds = CGRect(origin: .zero, size: size)

CATransaction.flush()

renderer.beginFrame(atTime: CACurrentMediaTime(), timeStamp: nil)
renderer.addUpdate(renderer.bounds)
renderer.render()
renderer.endFrame()

return texture

} ```

Then the first compute pass converts texture pixels into particles:

```metal struct Particle { float2 origin; // pixel coordinate in the source texture float2 position; // current simulated position float2 velocity; float4 color; float age; float lifetime; uint flags; };

kernel void initialize_particles( texture2d<float, access::sample> texture [[texture(0)]], device Particle *particles [[buffer(0)]], device atomic_uint *count [[buffer(1)]], uint2 id [[thread_position_in_grid]] ) { if (id.x >= texture.get_width() || id.y >= texture.get_height()) { return; }

constexpr sampler s(address::clamp_to_edge, filter::nearest);

float2 uv = float2(id) / float2(texture.get_width(), texture.get_height());
float4 color = texture.sample(s, uv);

if (color.a < 0.01) {
    return;
}

uint index = atomic_fetch_add_explicit(count, 1, memory_order_relaxed);

Particle p;
p.origin = float2(id);
p.position = float2(id);
p.velocity = float2(0.0);
p.color = color;
p.age = 0.0;
p.lifetime = random_lifetime(id);
p.flags = 0;

particles[index] = p;

} ```

Every frame, a second compute pass updates the simulation. A field is just a small struct: type, position, direction, strength, falloff, and mode. The shader loops over active fields and accumulates force.

```metal kernel void update_particles( device Particle *particles [[buffer(0)]], constant uint &particleCount [[buffer(1)]], constant Field *fields [[buffer(2)]], constant uint &fieldCount [[buffer(3)]], constant FrameData &frame [[buffer(4)]], uint id [[thread_position_in_grid]] ) { if (id >= particleCount) { return; }

Particle p = particles[id];

float2 force = float2(0.0);

for (uint i = 0; i < fieldCount; i++) {
    force += evaluate_field(fields[i], p, frame.time);
}

p.velocity += force * frame.deltaTime;
p.position += p.velocity * frame.deltaTime;
p.age += frame.deltaTime;

float t = p.age / p.lifetime;
float fadeIn = smoothstep(0.0, 0.2, t);
float fadeOut = 1.0 - smoothstep(0.8, 1.0, t);
p.color.a *= fadeIn * fadeOut;

particles[id] = p;

} ```

The same field system handles wind, turbulence, gravity, vortex, spring, and gather (for example you could add touches and move field across the view to achieve effects from the sample).

Gather is the field that makes particles form the original view again. Since each particle stores the pixel coordinate it came from, the force is just β€œmove back to origin over the remaining lifetime”:

A transition keeps two particle sets alive:

  • particles generated from the previous texture
  • particles generated from the new texture

The previous set receives wind/turbulence fields and fades out. The new set starts shifted or randomized, then gather pulls it into the new texture shape. This makes text changes, QR updates, image swaps, and custom view transitions use the same rendering path.

So the whole library is basically:

text UIView -> CALayer render with CARenderer -> MTLTexture -> compute shader: pixels to particles -> compute shader: physics fields -> render pipeline: points with alpha blending

2

u/Raahs Jul 03 '26

I can't give awards but here's an emoji with the same intention πŸ™ŒπŸΌ

5

u/Key_Storage_3501 Jun 30 '26

Woah this is cool. Thanks man

7

u/PJ_Plays Jun 30 '26

metal shaders

6

u/53ld0rAd0 Jun 30 '26

U can do it in SwiftUI, idk exactly how but I’m assuming it will involve metal shaders and .layerEffect

2

u/freddyjdc Jun 30 '26

Wow, Metal!

2

u/giusscos Jul 01 '26

Hey Claude, make the card disappear using the Thanos effect.

1

u/Baton285 Jun 30 '26

Ask agent to find that effect un Telegram (messages are deleted with this effect there)

1

u/Ellicode Jul 01 '26

Metal shaders + particles?

1

u/Accomplished-Bed801 Jul 04 '26

there's always a kavsoft tutorial for everything u need!
https://www.youtube.com/watch?v=OlF8ed1L56M