From 2822612cbb16fab47388c3be89629aeb15713ab4 Mon Sep 17 00:00:00 2001 From: Gregory Moskaliuk Date: Sun, 18 Oct 2026 09:46:51 +0200 Subject: [PATCH] Skip layer invalidation when an iOS View only changes position `RCTViewComponentView` requested a full `invalidateLayer` on every layout metrics update, including updates where only the frame origin changed. Everything the invalidation derives is in the view's own coordinates, so a view that merely moved rebuilt its shadow path, border rendering and background, box-shadow and mask layers for nothing. The hover shape is the exception, since iOS evaluates it in the superview's space, so views with `cursor: pointer` keep invalidating on moves. Co-Authored-By: Claude Fable 5.1 --- .../ComponentViews/View/RCTViewComponentView.mm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 582557336d61..9da159401707 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -700,8 +700,16 @@ - (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics // the oldLayoutMetrics parameter (which comes from the shadow tree). auto previousFrameSize = _layoutMetrics.frame.size; + // A relayout that only moves the view leaves everything `invalidateLayer` + // derives unchanged, since it works in the view's own coordinates. The hover + // shape is the exception: iOS evaluates it in the superview's space. + auto movedLayoutMetrics = layoutMetrics; + movedLayoutMetrics.frame.origin = _layoutMetrics.frame.origin; + BOOL const onlyMoved = movedLayoutMetrics == _layoutMetrics; _layoutMetrics = layoutMetrics; - _needsInvalidateLayer = YES; + if (!onlyMoved || _props->cursor == Cursor::Pointer) { + _needsInvalidateLayer = YES; + } _borderLayer.frame = self.layer.bounds;