From f83a5891b0e56ab72de82742178dc571c15940fa Mon Sep 17 00:00:00 2001 From: Dawid Date: Mon, 19 Aug 2024 10:44:50 +0200 Subject: [PATCH 1/6] fix drawing borders on old arch, RCTCreateMaskLayer refactoring --- .../View/RCTViewComponentView.mm | 13 +---- .../React/Views/RCTBorderDrawing.h | 6 +++ .../React/Views/RCTBorderDrawing.m | 13 +++++ packages/react-native/React/Views/RCTView.m | 52 ++++++++++++------- 4 files changed, 54 insertions(+), 30 deletions(-) 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 883e91b5d130..996de0afda97 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -814,7 +814,7 @@ - (void)invalidateLayer } else { RCTCornerInsets cornerInsets = RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero); - maskLayer = [self createMaskLayer:self.bounds cornerInsets:cornerInsets]; + maskLayer = RCTCreateMaskLayer(self.bounds, cornerInsets); } } @@ -829,7 +829,7 @@ - (void)invalidateLayer // 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 = RCTCreateMaskLayer(subview.bounds, cornerInsets); } } } @@ -927,15 +927,6 @@ - (void)invalidateLayer } } -- (CAShapeLayer *)createMaskLayer:(CGRect)bounds cornerInsets:(RCTCornerInsets)cornerInsets -{ - CGPathRef path = RCTPathCreateWithRoundedRect(bounds, cornerInsets, nil); - CAShapeLayer *maskLayer = [CAShapeLayer layer]; - maskLayer.path = path; - CGPathRelease(path); - return maskLayer; -} - - (void)clearExistingGradientLayers { if (_gradientLayers == nil) { diff --git a/packages/react-native/React/Views/RCTBorderDrawing.h b/packages/react-native/React/Views/RCTBorderDrawing.h index 4c92868f7c60..8cd9b10bbcc6 100644 --- a/packages/react-native/React/Views/RCTBorderDrawing.h +++ b/packages/react-native/React/Views/RCTBorderDrawing.h @@ -65,3 +65,9 @@ RCT_EXTERN UIImage *RCTGetBorderImage( RCTBorderColors borderColors, CGColorRef backgroundColor, BOOL drawToEdge); + +/** + * Create a CAShapeLayer that contains a path with specified bounds and corner insets without + * any affine transformations. + */ +RCT_EXTERN CAShapeLayer *RCTCreateMaskLayer(CGRect bounds, RCTCornerInsets cornerInsets); diff --git a/packages/react-native/React/Views/RCTBorderDrawing.m b/packages/react-native/React/Views/RCTBorderDrawing.m index fa2f5e50cb59..e8126474b60e 100644 --- a/packages/react-native/React/Views/RCTBorderDrawing.m +++ b/packages/react-native/React/Views/RCTBorderDrawing.m @@ -530,3 +530,16 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn return nil; } + +CAShapeLayer *RCTCreateMaskLayer(CGRect bounds, RCTCornerInsets cornerInsets) +{ + CGPathRef path = RCTPathCreateWithRoundedRect( + bounds, + cornerInsets, + nil); + CAShapeLayer *maskLayer = [CAShapeLayer layer]; + maskLayer.path = path; + CGPathRelease(path); + return maskLayer; +} + diff --git a/packages/react-native/React/Views/RCTView.m b/packages/react-native/React/Views/RCTView.m index 7b6f5e7a6d43..9fa33c962856 100644 --- a/packages/react-native/React/Views/RCTView.m +++ b/packages/react-native/React/Views/RCTView.m @@ -923,26 +923,40 @@ static void RCTUpdateHoverStyleForView(RCTView *view) - (void)updateClippingForLayer:(CALayer *)layer { - CALayer *mask = nil; - CGFloat cornerRadius = 0; - - if (self.clipsToBounds) { - const RCTCornerRadii cornerRadii = [self cornerRadii]; - if (RCTCornerRadiiAreEqual(cornerRadii)) { - cornerRadius = cornerRadii.topLeft; - - } else { - CAShapeLayer *shapeLayer = [CAShapeLayer layer]; - CGPathRef path = - RCTPathCreateWithRoundedRect(self.bounds, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL); - shapeLayer.path = path; - CGPathRelease(path); - mask = shapeLayer; + CALayer *mask = nil; + CGFloat cornerRadius = 0; + + if (self.clipsToBounds) { + const RCTCornerRadii cornerRadii = [self cornerRadii]; + if (RCTCornerRadiiAreEqual(cornerRadii)) { + cornerRadius = cornerRadii.topLeft; + + } else { + RCTCornerInsets cornerInsets = RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero); + mask = RCTCreateMaskLayer(self.bounds, cornerInsets); + } + } + + layer.cornerRadius = cornerRadius; + layer.mask = mask; + + for (UIView *subview in self.subviews) { + if ([subview isKindOfClass:[UIImageView class]]) { + const RCTCornerRadii cornerRadii = [self cornerRadii]; + const UIEdgeInsets borderInsets = [self bordersAsInsets]; + RCTCornerInsets cornerInsets = RCTGetCornerInsets(cornerRadii, borderInsets); + + // 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. + CGRect pathBounds = subview.bounds; + pathBounds.size.width -= borderInsets.left + borderInsets.right; + pathBounds.size.height -= borderInsets.top + borderInsets.bottom; + pathBounds.origin.x += borderInsets.left; + pathBounds.origin.y += borderInsets.top; + + subview.layer.mask = RCTCreateMaskLayer(pathBounds, cornerInsets); + } } - } - - layer.cornerRadius = cornerRadius; - layer.mask = mask; } #pragma mark Border Color From 7d711c12badd179f40072ca82f6e0d8264f63c68 Mon Sep 17 00:00:00 2001 From: Dawid Date: Tue, 20 Aug 2024 16:18:28 +0200 Subject: [PATCH 2/6] splitting background and border into separate layers on Fabric --- .../View/RCTViewComponentView.mm | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) 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 996de0afda97..9b39eac52ea2 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -33,6 +33,7 @@ @implementation RCTViewComponentView { __weak CALayer *_borderLayer; CALayer *_boxShadowLayer; CALayer *_filterLayer; + CALayer *_backgroundLayer; NSMutableArray *_gradientLayers; BOOL _needsInvalidateLayer; BOOL _isJSResponder; @@ -739,6 +740,8 @@ - (void)invalidateLayer (*borderMetrics.borderColors.left).getUIColor() != nullptr)); CGColorRef backgroundColor = [_backgroundColor resolvedColorWithTraitCollection:self.traitCollection].CGColor; + [_backgroundLayer removeFromSuperlayer]; + _backgroundLayer = nil; if (useCoreAnimationBorderRendering) { layer.mask = nil; @@ -749,24 +752,35 @@ - (void)invalidateLayer layer.borderColor = borderColor; CGColorRelease(borderColor); layer.cornerRadius = (CGFloat)borderMetrics.borderRadii.topLeft; - layer.cornerCurve = CornerCurveFromBorderCurve(borderMetrics.borderCurves.topLeft); - layer.backgroundColor = backgroundColor; } else { if (!_borderLayer) { CALayer *borderLayer = [CALayer new]; - borderLayer.zPosition = -1024.0f; + borderLayer.zPosition = 100; borderLayer.frame = layer.bounds; borderLayer.magnificationFilter = kCAFilterNearest; [layer addSublayer:borderLayer]; _borderLayer = borderLayer; } + + if (backgroundColor) { + CALayer *backgroundLayer = [CALayer new]; + backgroundLayer.zPosition = -1024.0f; + backgroundLayer.frame = layer.bounds; + // backgroundLayer.magnificationFilter = kCAFilterNearest; + [layer addSublayer:backgroundLayer]; + _backgroundLayer = backgroundLayer; + _backgroundLayer.backgroundColor = backgroundColor; + } layer.backgroundColor = nil; layer.borderWidth = 0; layer.borderColor = nil; layer.cornerRadius = 0; + + UIColor *transparentColor = [UIColor clearColor]; + CGColorRef transparentBackgroundColor = [transparentColor resolvedColorWithTraitCollection:self.traitCollection].CGColor; RCTBorderColors borderColors = RCTCreateRCTBorderColorsFromBorderColors(borderMetrics.borderColors); UIImage *image = RCTGetBorderImage( @@ -775,9 +789,9 @@ - (void)invalidateLayer RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths), borderColors, - backgroundColor, + transparentBackgroundColor, self.clipsToBounds); - + RCTReleaseRCTBorderColors(borderColors); if (image == nil) { @@ -811,27 +825,18 @@ - (void)invalidateLayer if (borderMetrics.borderRadii.isUniform()) { // In this case we can simply use `cornerRadius` exclusively. cornerRadius = borderMetrics.borderRadii.topLeft; + } else { RCTCornerInsets cornerInsets = RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero); maskLayer = RCTCreateMaskLayer(self.bounds, cornerInsets); } + } else if (borderMetrics.borderRadii.isUniform()) { + _backgroundLayer.cornerRadius = borderMetrics.borderRadii.topLeft; } layer.cornerRadius = cornerRadius; layer.mask = maskLayer; - - for (UIView *subview in self.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 = RCTCreateMaskLayer(subview.bounds, cornerInsets); - } - } } [_filterLayer removeFromSuperlayer]; From 5c19d1de23a6f81e6a06882001e4534f10eb5246 Mon Sep 17 00:00:00 2001 From: Dawid Date: Tue, 20 Aug 2024 17:00:14 +0200 Subject: [PATCH 3/6] background layer refactor --- .../ComponentViews/View/RCTViewComponentView.mm | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) 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 9b39eac52ea2..db5f4328abbb 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -757,7 +757,7 @@ - (void)invalidateLayer } else { if (!_borderLayer) { CALayer *borderLayer = [CALayer new]; - borderLayer.zPosition = 100; + borderLayer.zPosition = 1024.0f; borderLayer.frame = layer.bounds; borderLayer.magnificationFilter = kCAFilterNearest; [layer addSublayer:borderLayer]; @@ -768,7 +768,7 @@ - (void)invalidateLayer CALayer *backgroundLayer = [CALayer new]; backgroundLayer.zPosition = -1024.0f; backgroundLayer.frame = layer.bounds; - // backgroundLayer.magnificationFilter = kCAFilterNearest; + backgroundLayer.magnificationFilter = kCAFilterNearest; [layer addSublayer:backgroundLayer]; _backgroundLayer = backgroundLayer; _backgroundLayer.backgroundColor = backgroundColor; @@ -821,18 +821,21 @@ - (void)invalidateLayer // Stage 2.5. Custom Clipping Mask CAShapeLayer *maskLayer = nil; CGFloat cornerRadius = 0; - if (self.clipsToBounds) { - if (borderMetrics.borderRadii.isUniform()) { + BOOL isBorderRadiiUniform = borderMetrics.borderRadii.isUniform(); + if (self.clipsToBounds && isBorderRadiiUniform) { // In this case we can simply use `cornerRadius` exclusively. cornerRadius = borderMetrics.borderRadii.topLeft; - - } else { + } else if (self.clipsToBounds) { RCTCornerInsets cornerInsets = RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero); maskLayer = RCTCreateMaskLayer(self.bounds, cornerInsets); - } } else if (borderMetrics.borderRadii.isUniform()) { _backgroundLayer.cornerRadius = borderMetrics.borderRadii.topLeft; + } else { + RCTCornerInsets cornerInsets = + RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero); + CAShapeLayer *backgroundMaskLayer = RCTCreateMaskLayer(self.bounds, cornerInsets); + _backgroundLayer.mask = backgroundMaskLayer; } layer.cornerRadius = cornerRadius; From c9ab37bb296520d673df14a6a7703cb5cf46e26e Mon Sep 17 00:00:00 2001 From: Dawid Date: Wed, 21 Aug 2024 16:40:29 +0200 Subject: [PATCH 4/6] remove transparent background color --- .../Mounting/ComponentViews/View/RCTViewComponentView.mm | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 db5f4328abbb..751dd1efc7dd 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -778,9 +778,6 @@ - (void)invalidateLayer layer.borderWidth = 0; layer.borderColor = nil; layer.cornerRadius = 0; - - UIColor *transparentColor = [UIColor clearColor]; - CGColorRef transparentBackgroundColor = [transparentColor resolvedColorWithTraitCollection:self.traitCollection].CGColor; RCTBorderColors borderColors = RCTCreateRCTBorderColorsFromBorderColors(borderMetrics.borderColors); UIImage *image = RCTGetBorderImage( @@ -789,7 +786,7 @@ - (void)invalidateLayer RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths), borderColors, - transparentBackgroundColor, + nil, self.clipsToBounds); RCTReleaseRCTBorderColors(borderColors); From 127f7fdca751d30c95548b663846eac808dd77ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ma=C5=82ecki?= Date: Thu, 22 Aug 2024 14:24:44 +0200 Subject: [PATCH 5/6] Update packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm Co-authored-by: Riccardo Cipolleschi --- .../Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm | 2 +- 1 file changed, 1 insertion(+), 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 751dd1efc7dd..22b5cc89e901 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -826,7 +826,7 @@ - (void)invalidateLayer RCTCornerInsets cornerInsets = RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero); maskLayer = RCTCreateMaskLayer(self.bounds, cornerInsets); - } else if (borderMetrics.borderRadii.isUniform()) { + } else if (isBorderRadiiUniform) { _backgroundLayer.cornerRadius = borderMetrics.borderRadii.topLeft; } else { RCTCornerInsets cornerInsets = From 4b0902cb3b4b2412500f4587bdb6c109750cd85b Mon Sep 17 00:00:00 2001 From: Dawid Date: Fri, 23 Aug 2024 10:27:29 +0200 Subject: [PATCH 6/6] remove border drawing updates on fabric --- .../View/RCTViewComponentView.mm | 47 +++++++++---------- 1 file changed, 21 insertions(+), 26 deletions(-) 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 22b5cc89e901..996de0afda97 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -33,7 +33,6 @@ @implementation RCTViewComponentView { __weak CALayer *_borderLayer; CALayer *_boxShadowLayer; CALayer *_filterLayer; - CALayer *_backgroundLayer; NSMutableArray *_gradientLayers; BOOL _needsInvalidateLayer; BOOL _isJSResponder; @@ -740,8 +739,6 @@ - (void)invalidateLayer (*borderMetrics.borderColors.left).getUIColor() != nullptr)); CGColorRef backgroundColor = [_backgroundColor resolvedColorWithTraitCollection:self.traitCollection].CGColor; - [_backgroundLayer removeFromSuperlayer]; - _backgroundLayer = nil; if (useCoreAnimationBorderRendering) { layer.mask = nil; @@ -752,27 +749,19 @@ - (void)invalidateLayer layer.borderColor = borderColor; CGColorRelease(borderColor); layer.cornerRadius = (CGFloat)borderMetrics.borderRadii.topLeft; + layer.cornerCurve = CornerCurveFromBorderCurve(borderMetrics.borderCurves.topLeft); + layer.backgroundColor = backgroundColor; } else { if (!_borderLayer) { CALayer *borderLayer = [CALayer new]; - borderLayer.zPosition = 1024.0f; + borderLayer.zPosition = -1024.0f; borderLayer.frame = layer.bounds; borderLayer.magnificationFilter = kCAFilterNearest; [layer addSublayer:borderLayer]; _borderLayer = borderLayer; } - - if (backgroundColor) { - CALayer *backgroundLayer = [CALayer new]; - backgroundLayer.zPosition = -1024.0f; - backgroundLayer.frame = layer.bounds; - backgroundLayer.magnificationFilter = kCAFilterNearest; - [layer addSublayer:backgroundLayer]; - _backgroundLayer = backgroundLayer; - _backgroundLayer.backgroundColor = backgroundColor; - } layer.backgroundColor = nil; layer.borderWidth = 0; @@ -786,9 +775,9 @@ - (void)invalidateLayer RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths), borderColors, - nil, + backgroundColor, self.clipsToBounds); - + RCTReleaseRCTBorderColors(borderColors); if (image == nil) { @@ -818,25 +807,31 @@ - (void)invalidateLayer // Stage 2.5. Custom Clipping Mask CAShapeLayer *maskLayer = nil; CGFloat cornerRadius = 0; - BOOL isBorderRadiiUniform = borderMetrics.borderRadii.isUniform(); - if (self.clipsToBounds && isBorderRadiiUniform) { + if (self.clipsToBounds) { + if (borderMetrics.borderRadii.isUniform()) { // In this case we can simply use `cornerRadius` exclusively. cornerRadius = borderMetrics.borderRadii.topLeft; - } else if (self.clipsToBounds) { + } else { RCTCornerInsets cornerInsets = RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero); maskLayer = RCTCreateMaskLayer(self.bounds, cornerInsets); - } else if (isBorderRadiiUniform) { - _backgroundLayer.cornerRadius = borderMetrics.borderRadii.topLeft; - } else { - RCTCornerInsets cornerInsets = - RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero); - CAShapeLayer *backgroundMaskLayer = RCTCreateMaskLayer(self.bounds, cornerInsets); - _backgroundLayer.mask = backgroundMaskLayer; + } } layer.cornerRadius = cornerRadius; layer.mask = maskLayer; + + for (UIView *subview in self.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 = RCTCreateMaskLayer(subview.bounds, cornerInsets); + } + } } [_filterLayer removeFromSuperlayer];