Skip to content

Refresh the FlowLink tap closure in updateUIView - #52

Open
TaylorAanenson wants to merge 1 commit into
velos:mainfrom
TaylorAanenson:flowlink-stale-gesture-closure
Open

Refresh the FlowLink tap closure in updateUIView#52
TaylorAanenson wants to merge 1 commit into
velos:mainfrom
TaylorAanenson:flowlink-stale-gesture-closure

Conversation

@TaylorAanenson

Copy link
Copy Markdown

The problem

GestureContainer.makeCoordinator() stores onTap on the coordinator, and updateUIView is empty. SwiftUI creates a coordinator once per representable and reuses it across updates, so the UIButton keeps invoking the very first onTap it was handed.

That closure is { trigger() }, capturing the FlowLink struct from its first body evaluation — including its value and its label. Every later body pass builds a new struct that the button never sees.

Symptoms

For a FlowLink whose row is refreshed in place — same identity, new content — two things go wrong and neither is fixable from the call site:

  1. The pushed value is stale. trigger() appends the first-render value, so the destination receives outdated data. This bites hard in the common pattern of rendering a cached list and then swapping in a fetched payload under the same id: taps open the cached copy of a row for the rest of the session.
  2. The transition snapshot is stale. initSnapshots() renders the first-render label, so the image painted over the zoom shows content the row no longer displays. retakeSnapshots: true doesn't help — it faithfully re-renders the stale label.

Repro

A list whose rows come from a cache and are then replaced by fetched data under the same identity. Render it, let the fetch land so a row visibly updates, then tap that row — the destination gets the pre-fetch value. Deleting the cache so the row's first render is the fetched payload makes the same tap behave correctly.

The fix

One line, matching the standard UIViewRepresentable contract of pushing current state in updateUIView. The isPressed binding is left alone: it targets a stable @State box, so it doesn't go stale the way the escaping closure does.

GestureContainer's coordinator is created once and reused, and
updateUIView was empty, so the UIButton kept invoking the first onTap it
was ever handed. That closure captured the FlowLink struct from its
first body evaluation — including `value` and `label` — so a link whose
content is refreshed in place kept pushing the value it had at first
render and snapshotting the label it had at first render, however many
times SwiftUI re-rendered it.

Two visible symptoms in an app that renders a disk cache and then swaps
in a fetched payload under the same identity: taps open the cached copy
of a row for the rest of the session, and the transition snapshot shows
numbers the card no longer displays. retakeSnapshots can't help with the
latter — it faithfully re-renders the stale label.
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