diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm index 14a08b49cb7c..b38bc481cb95 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm @@ -495,10 +495,19 @@ - (void)updateState:(const State::Shared &)state oldState:(const State::Shared & } _contentSize = contentSize; - _containerView.frame = CGRect{RCTCGPointFromPoint(data.contentBoundingRect.origin), contentSize}; + + // `_containerView` is the scroll view's zooming view (see `viewForZoomingInScrollView:`), so while the user is + // pinch-zoomed in it carries a scale transform. Setting `frame` on a view whose `transform` is not the identity is + // undefined behavior, so lay it out through `bounds` and `center` instead. The scroll view must get the *zoomed* + // size, which is what UIScrollView itself keeps `contentSize` at while zooming. + CGPoint contentOrigin = RCTCGPointFromPoint(data.contentBoundingRect.origin); + _containerView.bounds = CGRect{CGPointZero, contentSize}; + CGSize zoomedContentSize = _containerView.frame.size; + _containerView.center = + CGPoint{contentOrigin.x + zoomedContentSize.width / 2, contentOrigin.y + zoomedContentSize.height / 2}; [self _preserveContentOffsetIfNeededWithBlock:^{ - self->_scrollView.contentSize = contentSize; + self->_scrollView.contentSize = zoomedContentSize; }]; }