From 27bc03f7d9c224bc2b7ba8d308a3515c2aa3fd7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 26 Jun 2026 18:24:41 +0200 Subject: [PATCH 1/3] fix(ios): rendering hitches --- .../Image/RCTImageComponentView.mm | 3 ++- .../View/RCTViewComponentView.mm | 18 +++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm index 2578acaf9cca..b93b8a239920 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm @@ -31,7 +31,8 @@ - (instancetype)initWithFrame:(CGRect)frame _props = defaultProps; _imageView = [RCTUIImageViewAnimated new]; - _imageView.clipsToBounds = YES; + // Note(Discord/Hanno): This should not be needed as the JS layer sets overflow:hidden! + // _imageView.clipsToBounds = YES; _imageView.contentMode = RCTContentModeFromImageResizeMode(defaultProps->resizeMode); _imageView.layer.minificationFilter = kCAFilterTrilinear; _imageView.layer.magnificationFilter = kCAFilterTrilinear; 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 a99f103a33e6..131fcf6b359f 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -1078,13 +1078,17 @@ - (void)invalidateLayer for (UIView *subview in self.currentContainerView.subviews) { if ([subview isKindOfClass:[UIImageView class]]) { - RCTCornerInsets cornerInsets = RCTGetCornerInsets( - RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), - RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths)); - - // If the subview is an image view, we have to apply the mask directly to the image view's layer, - // otherwise the image might overflow with the border radius. - subview.layer.mask = [self createMaskLayer:subview.bounds cornerInsets:cornerInsets]; + // Note(Discord/Hanno): The parent already applies the mask/clipping so this should be unnecessary. + // It has shown to cause CPU spikes + rendering hitches as this starts to cause offscreen draw passes in the render server. + // + // RCTCornerInsets cornerInsets = RCTGetCornerInsets( + // RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), + // RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths)); + + // // If the subview is an image view, we have to apply the mask directly to the image view's layer, + // // otherwise the image might overflow with the border radius. + // subview.layer.mask = [self createMaskLayer:subview.bounds cornerInsets:cornerInsets]; + subview.layer.mask = nil; } } } else if ( From 66ed378096f401b2533fdbf5c370ca80313042c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 29 Jun 2026 10:19:35 +0200 Subject: [PATCH 2/3] avoid setting overflow twice for Legacy Interop components --- .../RCTLegacyViewManagerInteropCoordinatorAdapter.mm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropCoordinatorAdapter.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropCoordinatorAdapter.mm index 39ff5f7bb72a..de777ab2b864 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropCoordinatorAdapter.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropCoordinatorAdapter.mm @@ -67,6 +67,17 @@ - (void)handleCommand:(NSString *)commandName args:(NSArray *)args NSMutableDictionary *diffedProps = [NSMutableDictionary new]; [newProps enumerateKeysAndObjectsUsingBlock:^(NSString *key, id newProp, __unused BOOL *stop) { + if ([key isEqualToString:@"overflow"]) { + /* + - RCTLegacyViewManagerInteropComponentView is itself a Fabric RCTViewComponentView, so super applies Fabric ViewProps like overflow, border radius, clipping, etc. to the wrapper. + - LegacyViewManagerInteropViewProps also stores the raw props in otherProps. + - RCTLegacyViewManagerInteropComponentView then forwards otherProps to the Paper view via _adapter setProps. + - Paper’s RCTViewManager handles overflow by setting view.clipsToBounds. + So the same overflow: hidden semantics get applied twice: + */ + return; + } + id oldProp = _oldProps[key]; if ([self _prop:newProp isDifferentFrom:oldProp]) { diffedProps[key] = newProp; From 957f2f47c8d40309539617c15e664466bd9499b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 29 Jun 2026 18:57:00 +0200 Subject: [PATCH 3/3] Revert "avoid setting overflow twice for Legacy Interop components" This reverts commit 66ed378096f401b2533fdbf5c370ca80313042c5. --- .../RCTLegacyViewManagerInteropCoordinatorAdapter.mm | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropCoordinatorAdapter.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropCoordinatorAdapter.mm index de777ab2b864..39ff5f7bb72a 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropCoordinatorAdapter.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropCoordinatorAdapter.mm @@ -67,17 +67,6 @@ - (void)handleCommand:(NSString *)commandName args:(NSArray *)args NSMutableDictionary *diffedProps = [NSMutableDictionary new]; [newProps enumerateKeysAndObjectsUsingBlock:^(NSString *key, id newProp, __unused BOOL *stop) { - if ([key isEqualToString:@"overflow"]) { - /* - - RCTLegacyViewManagerInteropComponentView is itself a Fabric RCTViewComponentView, so super applies Fabric ViewProps like overflow, border radius, clipping, etc. to the wrapper. - - LegacyViewManagerInteropViewProps also stores the raw props in otherProps. - - RCTLegacyViewManagerInteropComponentView then forwards otherProps to the Paper view via _adapter setProps. - - Paper’s RCTViewManager handles overflow by setting view.clipsToBounds. - So the same overflow: hidden semantics get applied twice: - */ - return; - } - id oldProp = _oldProps[key]; if ([self _prop:newProp isDifferentFrom:oldProp]) { diffedProps[key] = newProp;