ReviewOS

craft-native/craft

An absolutely-positioned sibling of a ScrollView pins on one native screen and scrolls on another, with identical markup

#356
Closed glennmichael123 opened this 9 days ago ·
9 days ago

On 0.0.173, a position: absolute child of a native screen's root sometimes pins to the screen and sometimes scrolls away with the ScrollView's content. The two screens I can compare have identical structure, so I cannot make it depend on anything in the markup.

The shape, on both:

<View class="flex-1 …">                       <!-- root -->
  <ScrollView class="flex-1" @refresh :refreshing>…</ScrollView>
  <View class="absolute bottom-4 right-4 h-14 w-14 rounded-full">…</View>
</View>

Today — the absolute child holds its position: measured at y≈730 pt before scrolling and y≈730 pt after scrolling a long page. Correct.

Progress — it is not on screen at the top of the page at all, and appears near the bottom only once you scroll to the end of the content, half behind the floating tab bar. It is travelling with the content, i.e. the root's height has resolved to the content height rather than the viewport.

Both screens: root flex-1, ScrollView flex-1 with the same @refresh / :refreshing props and nothing else, the absolute child as the second and last root child. I diffed them line by line; the ScrollView's attributes are character-for-character the same.

What I ruled out, each with a build and a look on an iPhone 17 Pro (iOS 26.5):

  • Root class source. Today writes the classes literally, Progress and Calendar use a bound :class. Calendar also uses the bound form and pins correctly, so it is not that.
  • An explicit height on the root. h-full added to the root's bound class (verified present in the compiled bundle) changes nothing.
  • A trailing spacer. Today and Calendar end their scroll content with <View class="h-24" />; Progress does not. Adding one changes nothing.
  • Content wrapping. Today's scroll content sits inside a <Fragment>, Progress's does not. Wrapping Progress's content in a Fragment changes nothing.
  • The component not rendering. Its testID appears the expected number of times in the built bundle, and it does render — just in the wrong place.

So the two screens that pin are the one with a Fragment and the one with stickyHeaderIndices, and the three that do not have plain content. That is a correlation I could not turn into a cause, and it may be coincidence.

Why I think it is the root's height. rootStack is pinned to the safe area on all four edges in viewDidLoad, so the root should have a definite height on every screen, and flex-1 should then clamp the ScrollView. CraftNativeScrollView.measure returns height ?? contentFitting(…).height — so wherever the height arrives as nil the scroll view reports its content height, and a parent sizing to it grows past the viewport. On the screens that misbehave, that looks like what happens.

Happy to put a reduced screen in a branch if that helps — the app reproduces it reliably on three screens and not on two, with the same markup, so it should reduce.

I have shipped the element on the two screens that place it correctly and left it off the other three.

9 days ago

Correction: the iOS root viewport fix is on main (40f7623), with maintained navigation coverage on both hosts and a plain-View root unit regression added in 68084b1. The test scrolls overflowing content and asserts the absolute sibling keeps both viewport edges. A simulator run is still pending because CoreSimulator is unavailable here; leaving the issue open until a published build and runtime check.

9 days ago

Fixed in 0.0.187 — thank you.

Re-tested on the same three screens it failed on, with the same markup the two working screens use (an absolutely positioned sibling of the ScrollView, inside the screen's root View):

Progress   scrolled to the bottom   button held its position
Health     scrolled to the bottom   button held its position
Store      scrolled to the bottom   button held its position

iPhone 17 Pro, iOS 26.5, Craft 0.0.187, stx 0.2.530. Filed against 0.0.173.

The button is now on all five tabs of the app rather than the two that happened to work.

Sign in to comment on this issue.