ReviewOS

craft-native/craft

ios(hybrid): a web-to-web push animates two blank screens, because the shared WKWebView moves before the transition

#358
Closed glennmichael123 opened this 8 days ago ·
8 days ago

A push between two web entries animates two empty panels. The outgoing screen has lost the web view before the slide starts, and the incoming one is holding it invisible until the page catches up, so for the length of the animation both screens are a flat background colour and the content appears afterwards.

Reported from an app in production as "sliding from page to page is kinda bad".

Why, from the shell's own comments

One WKWebView serves every web entry. CraftHybrid.swift:11 states the design: each tab keeps one UINavigationController whose stack mixes native screens and web entries, "and the one WKWebView moves into whichever web entry is on screen".

Three things then line up against the animation:

  1. A web slot has no content of its own. CraftHybridWebSlot (:968) sets only view.backgroundColor (:990), and its class comment says it outright (:967): "the others are empty until they are shown again."

  2. The move happens before the animation, not after. The incoming slot adopts the web view in viewWillAppear (:995), and moveWeb (:1219) calls removeFromSuperview() and removeFromParent() on the old owner. UIKit runs viewWillAppear ahead of the push transition, so the outgoing screen is stripped at the moment it starts sliding.

  3. The incoming slot deliberately shows nothing until the page arrives. webHost.view.alpha = slot.awaitingPage ? 0 : 1 (:1335), revealed in reveal(_:) (:1442) once the reported path matches. The comment at :975 is explicit that the entry shows its background "not the page it is leaving, until it has".

Each of those is reasonable alone. Together they mean a web to web push has nothing to animate.

What it looks like

PushOutgoingIncoming
web to webblank, the web view already leftblank until the page reports the new path
native to webdrawn natively, correctblank until the page arrives
web to nativeblankdrawn natively, correct

Web to web is the common case inside an app that is mostly its web page, and it is the one with no correct half.

Shape of a fix

The standard hybrid remedy is to cover the move with a visual snapshot, so the animation has something to animate:

  • take snapshotView(afterScreenUpdates: false) of the web view before moveWeb strips it, and leave that view in the outgoing slot for the duration of the transition, removing it on didShow. The outgoing screen then does what UIKit does natively: it shows the page it is leaving while it parallaxes away.
  • while awaitingPage holds the live view at alpha 0, show something other than a bare background in the incoming slot. A snapshot of the destination when one is available, otherwise the existing background, but the outgoing half is the larger win and is unconditional.

snapshots already in this file is unrelated: it is CraftSnapshots, a JSON store for native screens, not a visual snapshot. Nothing in packages/ios/templates calls snapshotView today.

Verification gap, which is most of the work

packages/ios/scripts/test-native-navigation.ts drives a real simulator, but its fixture is WebView-free by design and asserts the app exposes no WebView, so nothing exercises a hybrid web slot transition. A fix here is visual, and a visual fix that cannot be run cannot be claimed, so a hybrid fixture comes first: two web entries and one native entry in a tab, pushed and popped, asserting the outgoing screen is not a flat fill mid-transition.

Not Android

The Android shell has no equivalent. CraftNativeShell.kt.template routes nativeScreens and tabs but does not mix web entries into a native back stack with a shared WebView moved between them, so this is iOS hybrid only.

8 days ago

Closing this. The central claim is wrong, and the shell already does the thing I said was missing. Two line numbers I cited were misread, and reading the push path properly reverses the conclusion.

A web-to-web navigation never pushes a slot

I claimed the broken case was a push between two web entries. There is no such push. A new CraftHybridWebSlot is only pushed when the page moves while a native screen is on top (:1517, guarded by if top is CraftNativeScreenController). The page script hands the shell navigations to native paths only; everything else the page draws itself, in the one web view, with its own router. So the common case I built the report around does not reach UIKit at all.

The outgoing screen is not stripped during the slide

I said the web view is taken away at viewWillAppear, before the animation, leaving the outgoing screen blank. The adopt/park that matters is at :1635, inside didShow, which runs after the transition, not :1607 as I wrote. So on a push from a web entry to a native screen the web view stays where it is for the length of the slide and the outgoing screen parallaxes away with its content intact, which is correct.

And the incoming half is already handled

pushWeb (:1447) says it in its own docstring: "The web view, pushed: the page goes to path first, without its own slide, then the entry slides in over the screen that opened it." It navigates the page and only pushes the slot in the completion, so by the time the animation starts the page is at the new path, depth is non-nil, awaitingPage stays false, and the live content is visible at alpha 1 throughout.

awaitingPage = true is reached only when the page does not answer (depth == nil), and that branch is a deliberate fallback with a 3 second cap and a 0.15s fade on arrival.

What survives

Only that fallback: a page slow to answer gives a web entry that slides in on its background colour and fades its content in afterwards. That is intentional, bounded, and not what was reported.

So the hybrid shell is not the source of "sliding from page to page is kinda bad". Since a web-to-web navigation is drawn entirely by the page, the slide being complained about is the stx router's own screen animation running inside WKWebView, and that is where I am taking it. If a hybrid fixture is wanted later for its own sake, the gap named here is real: scripts/test-native-navigation.ts is WebView-free by design, so nothing exercises a hybrid transition.

My apologies for the noise. I filed from reading rather than running, and two mis-cited lines carried the rest.

Sign in to comment on this issue.