Skip to content

Rewrite IFViewAnimator using CoreAnimation (fixes #46)#51

Open
angstsmurf wants to merge 1 commit into
TobyLobster:extensionsfrom
angstsmurf:pr-coreanimation-viewanimator
Open

Rewrite IFViewAnimator using CoreAnimation (fixes #46)#51
angstsmurf wants to merge 1 commit into
TobyLobster:extensionsfrom
angstsmurf:pr-coreanimation-viewanimator

Conversation

@angstsmurf

@angstsmurf angstsmurf commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #46 — the source ⇄ Contents and next/previous-section transitions in the Source pane were choppy.

IFViewAnimator drove its transition from a 100 Hz NSTimer that called -setNeedsDisplay: on every tick, with -drawRect: manually compositing two cached bitmaps on the CPU each frame. Because the timer runs on the main thread and isn't vsync-synced, it is easily starved and the animation stutters.

This rewrites the animator to use CoreAnimation: the "from" and "to" views are snapshotted into two CALayers and slid (or cross-faded) with CABasicAnimation, which is GPU-composited and display-link synced.

Details

  • The public API is unchanged, so the callers in IFSourcePage.m need no changes.
  • The four slide directions actually used (IFAnimateLeft/Right/Up/Down) are preserved exactly — I traced each one's geometry from the old -drawRect: so the direction of motion matches the previous behaviour.
  • The unused IFAnimateCrossFade, IFFloatIn, and IFFloatOut styles fall back to a simple cross-fade so the API keeps working.
  • Layer contentsScale is set to the window's backing scale factor, so snapshots stay crisp on Retina displays (the old CPU path did not account for this).
  • The old mid-animation "recache" hack in -drawRect: (re-snapshotting the target if it still wanted display in the first 25%) is replaced by forcing layoutSubtreeIfNeeded + displayIfNeeded before taking the snapshot.
  • Re-entrancy: finishAnimation tears down the layers and is guarded by the existing originalView != nil check, so the CATransaction completion block is a safe no-op if the animation was abandoned early or the animator was deallocated.

Net change is a single file, +145 / −265 lines.

Testing

Built and run in the Xcode IDE. Verified visually:

  • Source ⇄ Contents toggle (both directions).
  • Next / previous section navigation, including at the first/last section boundaries.
  • First responder is restored to the text view after the transition completes.

Base branch is extensions since that is where current development seems to live.

🤖 Generated with Claude Code

The view-switching animation (source <-> Contents, and next/previous
section) was driven by a 100Hz NSTimer that called -setNeedsDisplay:
every tick, with -drawRect: manually compositing two cached bitmaps on
the CPU. Not vsync-synced and easily starved on the main thread, which
made the transition choppy.

Replace the timer/drawRect machinery with CoreAnimation: snapshot the
'from' and 'to' views into two CALayers and slide (or cross-fade) them
with CABasicAnimation, GPU-composited and display-link synced. Layer
contentsScale is set to the window backing scale so snapshots stay
crisp on Retina.

The public API is unchanged, so callers in IFSourcePage.m need no
changes. The old mid-animation 'recache' hack is replaced by forcing
layout/display before snapshotting. The unused Left/Right/Up/Down slide
directions are preserved exactly; the unused CrossFade/FloatIn/FloatOut
styles fall back to a cross-fade.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@angstsmurf
angstsmurf marked this pull request as ready for review July 11, 2026 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant