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 4e58cbd653c..cca2f1d80f0 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 @@ using namespace facebook::react; __weak CALayer *_borderLayer; CALayer *_boxShadowLayer; CALayer *_filterLayer; + NSMutableArray *_gradientLayers; BOOL _needsInvalidateLayer; BOOL _isJSResponder; BOOL _removeClippedSubviews; @@ -458,6 +459,11 @@ using namespace facebook::react; } } + // `linearGradient` + if (oldViewProps.backgroundImage != newViewProps.backgroundImage) { + needsInvalidateLayer = YES; + } + // `boxShadow` if (oldViewProps.boxShadow != newViewProps.boxShadow) { needsInvalidateLayer = YES; @@ -830,6 +836,56 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle) [self.layer addSublayer:_filterLayer]; } + [self clearExistingGradientLayers]; + auto backgroundImage = _props->backgroundImage; + if (backgroundImage.empty()) { + return; + } + for (const auto &gradient : backgroundImage) { + CAGradientLayer *gradientLayer = [CAGradientLayer layer]; + NSMutableArray *colors = [NSMutableArray array]; + NSMutableArray *locations = [NSMutableArray array]; + for (const auto &colorStop : gradient.colorStops) { + if (colorStop.position.has_value()) { + auto location = @(colorStop.position.value()); + UIColor *color = RCTUIColorFromSharedColor(colorStop.color); + [colors addObject:(id)color.CGColor]; + [locations addObject:location]; + } + } + gradientLayer.startPoint = CGPointMake(gradient.startX, gradient.startY); + gradientLayer.endPoint = CGPointMake(gradient.endX, gradient.endY); + + if (locations.count > 0) { + gradientLayer.locations = locations; + } + gradientLayer.colors = colors; + gradientLayer.frame = layer.bounds; + + // border styling to work with gradient layers + if (useCoreAnimationBorderRendering) { + gradientLayer.borderWidth = layer.borderWidth; + gradientLayer.borderColor = layer.borderColor; + gradientLayer.cornerRadius = layer.cornerRadius; + gradientLayer.cornerCurve = layer.cornerCurve; + } else { + CAShapeLayer *maskLayer = [CAShapeLayer layer]; + CGPathRef path = RCTPathCreateWithRoundedRect( + self.bounds, + RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero), + nil); + maskLayer.path = path; + CGPathRelease(path); + gradientLayer.mask = maskLayer; + } + + // border layer should appear above gradient layers to make sure that the border is visible + gradientLayer.zPosition = _borderLayer.zPosition - 1; + + [self.layer addSublayer:gradientLayer]; + [_gradientLayers addObject:gradientLayer]; + } + [_boxShadowLayer removeFromSuperlayer]; _boxShadowLayer = nil; if (!_props->boxShadow.empty()) { @@ -848,6 +904,18 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle) } } +- (void)clearExistingGradientLayers +{ + if (_gradientLayers == nil) { + _gradientLayers = [NSMutableArray new]; + return; + } + for (CAGradientLayer *gradientLayer in _gradientLayers) { + [gradientLayer removeFromSuperlayer]; + } + [_gradientLayers removeAllObjects]; +} + #pragma mark - Accessibility - (NSObject *)accessibilityElement diff --git a/packages/rn-tester/js/examples/LinearGradient/LinearGradientExample.js b/packages/rn-tester/js/examples/LinearGradient/LinearGradientExample.js index b06fc501ae0..0a3486adc36 100644 --- a/packages/rn-tester/js/examples/LinearGradient/LinearGradientExample.js +++ b/packages/rn-tester/js/examples/LinearGradient/LinearGradientExample.js @@ -126,4 +126,35 @@ exports.examples = [ ); }, }, + { + title: 'Gradient with uniform border style', + render(): React.Node { + return ( + + ); + }, + }, + { + title: 'Gradient with non-uniform border style', + render(): React.Node { + return ( + + ); + }, + }, ]; diff --git a/packages/rn-tester/js/utils/RNTesterList.ios.js b/packages/rn-tester/js/utils/RNTesterList.ios.js index fecfd4ee407..b41392a69a5 100644 --- a/packages/rn-tester/js/utils/RNTesterList.ios.js +++ b/packages/rn-tester/js/utils/RNTesterList.ios.js @@ -298,6 +298,10 @@ const APIs: Array = ([ key: 'FilterExample', module: require('../examples/Filter/FilterExample'), }, + { + key: 'LinearGradient', + module: require('../examples/LinearGradient/LinearGradientExample'), + }, { key: 'MixBlendModeExample', module: require('../examples/MixBlendMode/MixBlendModeExample'),