From 7ef7a7ad3a6ac4284f07fdb0964bafaee62fe1f8 Mon Sep 17 00:00:00 2001 From: Yevhenii Date: Fri, 16 Oct 2026 11:42:49 +0300 Subject: [PATCH] Leave a view untransformed on iOS when its transform is not finite --- .../React/Fabric/RCTConversions.h | 8 +++++ .../Mounting/RCTViewComponentViewTests.mm | 33 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/packages/react-native/React/Fabric/RCTConversions.h b/packages/react-native/React/Fabric/RCTConversions.h index eba3094e769f..d3e8f7295bbf 100644 --- a/packages/react-native/React/Fabric/RCTConversions.h +++ b/packages/react-native/React/Fabric/RCTConversions.h @@ -5,6 +5,8 @@ * LICENSE file in the root directory of this source tree. */ +#import + #import #import @@ -152,6 +154,12 @@ inline UIAccessibilityTraits RCTUIAccessibilityTraitsFromAccessibilityTraits( inline CATransform3D RCTCATransform3DFromTransformMatrix(const facebook::react::Transform &transformMatrix) { + // Core Animation needs a finite transform: a NaN in one hides the layer and can hide its siblings. + for (const auto value : transformMatrix.matrix) { + if (!std::isfinite(value)) { + return CATransform3DIdentity; + } + } return { (CGFloat)transformMatrix.matrix[0], (CGFloat)transformMatrix.matrix[1], diff --git a/packages/react-native/React/Tests/Mounting/RCTViewComponentViewTests.mm b/packages/react-native/React/Tests/Mounting/RCTViewComponentViewTests.mm index 09019e157173..2828481a744a 100644 --- a/packages/react-native/React/Tests/Mounting/RCTViewComponentViewTests.mm +++ b/packages/react-native/React/Tests/Mounting/RCTViewComponentViewTests.mm @@ -204,6 +204,39 @@ - (void)testHitTestAfterScaleTransitionedToZeroReturnsNil XCTAssertNil([view hitTest:CGPointMake(50, 50) withEvent:nil]); } +#pragma mark - non-finite transforms + +static RCTViewComponentView *makeViewWithTransform(const Transform &transform) +{ + RCTViewComponentView *view = [RCTViewComponentView new]; + auto props = std::make_shared(); + props->transform = transform; + [view updateProps:props oldProps:ViewShadowNode::defaultSharedProps()]; + return view; +} + +- (void)testFiniteTransformIsApplied +{ + RCTViewComponentView *view = makeViewWithTransform(Transform::Translate(5, 10, 0)); + + XCTAssertEqual(view.layer.transform.m41, 5.0); + XCTAssertEqual(view.layer.transform.m42, 10.0); +} + +- (void)testTransformWithNaNIsNotApplied +{ + RCTViewComponentView *view = makeViewWithTransform(Transform::Translate(NAN, 10, 0)); + + XCTAssertTrue(CATransform3DIsIdentity(view.layer.transform)); +} + +- (void)testTransformWithInfinityIsNotApplied +{ + RCTViewComponentView *view = makeViewWithTransform(Transform::Scale(INFINITY, 1, 1)); + + XCTAssertTrue(CATransform3DIsIdentity(view.layer.transform)); +} + #pragma mark - Full Keyboard Access focusability static RCTViewComponentView *makeViewWithRole(bool accessible, const std::string &accessibilityRole)