A photo that lifts off the viewfinder, hangs, and lands in the roll
If you'd like to learn more, please get in touch.
The photo is a physical thing. Press the shutter and the frame you were looking at lifts off the viewfinder, condenses into a thumbnail, is held up for a beat, and is then dropped into the roll. You watch it become a photo, and you watch where it goes.
Most camera apps confirm a capture by freezing the preview, or by flashing a thumbnail into a corner. Both cut the link between what you were looking at and what you just kept. This one keeps the link visible from the first frame to the last.
Three things had to be true for it to read as one motion rather than a chain of tricks.
Origin. The tile must visibly come from the viewfinder. Not appear near it. Come from it.
Physics, not keyframes. Nothing is animated along a path. Every frame is the output of an equation being solved. That is what makes the hang at the top feel like weight rather than an ease curve.
One continuous flow. The tile leaves on the same frame as the tap and lands without a swap, a blink, or a layout shift. Five taps in a row should look like five throws, not five transitions.
On the frame the shutter is pressed, three things happen before the photo exists.
The capture claims a sequence number and reserves a slot in the roll. The roll lays out one slot per capture taken, not per capture landed. So the stack rearranges now, while the tile is still on the viewfinder, and by the time the tile arrives its neighbours have been still for half a second.
The live preview frame is read from a cache, synchronously. The photo itself is hundreds of milliseconds away, so the thing that flies is the frame the sensor was showing at that instant.
A tile is created as a pixel-perfect duplicate of the viewfinder card: same position, same size, same corner radius. At this moment it is indistinguishable from the card underneath it.
The flight is driven by one shared CADisplayLink that exists only while something is animating. Each tick, every registered animation receives the interval to the next expected frame and integrates forward by exactly that amount. Layer writes happen inside a transaction with implicit actions disabled, so setting a position sets a position instead of starting a 0.25s animation on top of the physics.
The integrator runs in fixed 1/240s substeps using semi-implicit Euler. Large gaps (a hitch, backgrounding) are clamped so one frame can never take a giant unstable step. This is the same discipline the app's springs use, and it is stable at any stiffness the tuning sheet can produce.
State is position, velocity, and elapsed time. Acceleration is one expression, evaluated every substep:
w = smoothstep((t − hold) / blend) // handover weight, 0 → 1
aGx = −λ · vx // drag on the horizontal axis
aGy = g₀ · (1 − α · exp(−(vy/σ)²)) // gravity, dipped at the apex
aS = −k·(p − target) − c·v // a spring on the landing slot
a = (1 − w)·(aGx, aGy) + w·aS There are two force fields. A projectile field (drag and gravity) and an attractor field (a spring anchored on the slot). w cross-fades between them. It is zero through the rise and the hang, so the tile is a pure projectile, then ramps to one so the spring owns the landing.
Cross-fading instead of switching is what keeps momentum continuous: nothing is handed off, the velocity at the apex is simply what the spring inherits. And because gravity is gone by the time the spring settles, the tile rests exactly on the slot instead of sagging a steady-state offset below it.
Launch is an upward impulse, v₀ = √(2·g·h), derived from a pop height rather than set directly, so the knob is a distance you can see on screen.
The hang at the top is real physics before it is anything else. Under constant gravity the time to fall a distance d from the apex is √(2d/g), which means a projectile spends about a third of its rise inside the top ten percent of its arc. Thrown objects linger at the top on their own.
The Gaussian term exaggerates that. Gravity is scaled down near zero vertical velocity, and only there. Climbing fast and falling fast both sit in the tails of the curve, so the dip bites at the apex and nowhere else. Acceleration never steps, and nothing ever freezes. The hang is stretched, not paused.
X and Y are separate systems. They share elapsed time and nothing else.
Y is launched upward and bent by gravity, a quadratic. X is launched sideways at a small speed and bled off by drag, an exponential: vx(t) = vx₀ · e^(−λt). No shared progress parameter ties them together, so the path is a parametric curve rather than a point sliding along a line. Two different curves against the same clock is what makes it read as thrown rather than driven.
The sideways launch points away from the roll. The spring has to haul the tile back across on the way down, so the arc crosses its own launch line. It is a small lean, about 30 points, but it is the difference between a throw and a delivery.
While the tile rises it stops being the viewfinder and becomes a thumbnail.
Size, corner radius and border each run on their own critically damped curve. Size resolves fastest, so the tile stops being a picture of the card before it stops being card-shaped. The border grows in from nothing, since the card has none. Critical damping matters here: an overshoot would mean shrinking past the final size and rebounding, which reads as a glitch rather than as spring.
The layer's centre interpolates from the card's centre onto the physics position as the collapse completes. This is what let formation be added without touching a single flight constant. The physics never knew the tile was ever large.
By the apex the collapse is 98% done. So the hang is not a physics flourish. It is the beat between made and filed: the thumbnail is finished, held up, and then delivered.
w lifts off zero just past the hang, the spring takes over, and the tile falls. Peak speed is about 3700 points per second. At 120Hz that is 31 points per frame, most of the tile's own width, and it is where motion blur stops being decoration.
The model is a shutter. Open it for τ seconds and an object moving at v smears across L = v · τ. That is the entire physics, and τ is the only constant. 1/240s is a 180° shutter at 120Hz, the cinematic default. 1/120s smears exactly the gap between frames; past that you are stylising rather than simulating.
The length is driven by the downward velocity alone:
L = max(vy, 0) · τ That single expression is the whole gate. Rising gives zero. The apex gives zero, and correctly so, because the tile really is still there. Then it grows with the fall. There is no trigger, no threshold, and no state.
Core Animation has no directional blur, so the smear is drawn as copies of the tile laid back along the direction of travel. The count scales with the length so the spacing stays under a quarter of the tile and never bands. Alpha ramps up to an opaque head that sits exactly on the physics position, so the tile still lands where it should.
Somewhere during the fall the photo finishes processing. It is swapped into the flying tile right then, while the tile is small and smeared, because the live frame and the processed photo differ in exposure and white balance. Swapping them at rest on the slot reads as a blink. Swapping them mid-fall is invisible, and the handover at the end becomes an exchange of identical pixels.
The spring settles. The slot the tile reserved on the tap flips from an empty placeholder to the thumbnail: same position, same size, same pixels, and no layout change, because the roll rearranged a second ago.
The flying layer is held for two more frames, until the real tile is certainly on screen, and then removed. Overlapping two identical images costs nothing. A one-frame gap costs the whole illusion.