[iOS][Fabric] Pinch-zoomed ScrollView breaks (position jumps, cannot zoom back out) after its size changes

オープン
#58,499 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
25/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
停滞
技術スタック
ios, objective-c, react-native
領域
mobile

調査の方向性

まず RCTScrollViewComponentView の updateState:oldState: と prepareForRecycle から始め、その後 iOS で reproducer-react-native の例を実行します。ズーム中、およびサイズ変更や回転時の動作を確認します。サイズ変更後もスクロール位置が安定したままで、コンテンツを再び完全にズームアウトできれば完了です。

索引モデルが issue の本文から書いたものです。

説明

Component: ScrollView Needs: Triage :mag: Platform: iOS
Description

On iOS with the new architecture, a ScrollView with maximumZoomScale > 1 gets into a broken state whenever its size changes while the user is zoomed in (device rotation, or the layout around it changing):

  1. The scrolled position jumps to a different spot.
  2. The content can no longer be zoomed all the way back out; it stays stuck against one side or corner.

The only way to recover is to zoom all the way out in the broken state and then resize again (rotate back and forth). After that it works again until the next resize while zoomed.

Cause. RCTScrollViewComponentView returns _containerView from viewForZoomingInScrollView:, so while zoomed in that view carries UIScrollView's scale transform. In updateState:oldState: a content size change does

_containerView.frame = CGRect{RCTCGPointFromPoint(data.contentBoundingRect.origin), contentSize};
...
self->_scrollView.contentSize = contentSize;
  • Setting frame on a view whose transform is not the identity is undefined behavior per the UIView docs ("If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored"). In practice UIKit shrinks the container's bounds by the zoom scale, so when the user zooms back out to scale 1 the content is smaller than the scroll view and sits in a corner.
  • contentSize is set to the unzoomed size while zoomScale != 1. UIScrollView itself keeps contentSize at the zoomed size, so the offset is clamped to the wrong range and the position jumps.

The same code is on main today. prepareForRecycle already hints at it: "Invalidate cached content size so that updateState: recalculates the container frame after zoomScale reset (which may have mutated it in RTL)".

Fix: lay the container out through bounds/center and give the scroll view the zoomed content size. PR: https://github.com/react/react-native/pull/58498

Steps to reproduce
  1. Run the reproducer on an iOS device or simulator (new architecture is on by default).
  2. Pinch to zoom in (2x or more) and pan somewhere away from the top-left.
  3. Tap "Resize" (changes the ScrollView's height) or rotate the device.
  4. The position jumps. Pinch back out: the content stays stuck in a corner and does not fill the ScrollView.
  5. Zoom out fully, resize again: it recovers.
React Native Version

0.87.1

Affected Platforms

Runtime - iOS

Output of npx @react-native-community/cli info
Not run: the reproducer repository is a fresh `reproducer-react-native` template pinned to react-native 0.87.1, run on iOS 18 simulator/device.
Stacktrace or Logs
No crash or log output; the bug is visual.
MANDATORY Reproducer

https://github.com/mifi/reproducer-react-native

Screenshots and Videos

None yet.

主要言語
C++
スター
127k
フォーク
25.3k
PR マージ指標
30日以内にマージされた PR はありません

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

react/react-native のほかの issue

react/react-native の issue をすべて見る

似ている issue

C++ の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。