diff --git a/packages/react-native/React/Views/RCTBorderDrawing.h b/packages/react-native/React/Views/RCTBorderDrawing.h index fbc8407a535..da0c04d6aa9 100644 --- a/packages/react-native/React/Views/RCTBorderDrawing.h +++ b/packages/react-native/React/Views/RCTBorderDrawing.h @@ -35,6 +35,11 @@ typedef struct { UIColor *right; } RCTBorderColors; +/** + * Determine the largest border inset value. + */ +RCT_EXTERN CGFloat RCTMaxBorderInset(UIEdgeInsets borderInsets); + /** * Determine if the border widths, colors and radii are all equal. */ diff --git a/packages/react-native/React/Views/RCTBorderDrawing.m b/packages/react-native/React/Views/RCTBorderDrawing.m index a2d9ba27995..da99e0c58f4 100644 --- a/packages/react-native/React/Views/RCTBorderDrawing.m +++ b/packages/react-native/React/Views/RCTBorderDrawing.m @@ -10,6 +10,11 @@ static const CGFloat RCTViewBorderThreshold = 0.001; +CGFloat RCTMaxBorderInset(UIEdgeInsets borderInsets) +{ + return MAX(MAX(borderInsets.top, borderInsets.left), MAX(borderInsets.bottom, borderInsets.right)); +} + BOOL RCTBorderInsetsAreEqual(UIEdgeInsets borderInsets) { return ABS(borderInsets.left - borderInsets.right) < RCTViewBorderThreshold && @@ -415,8 +420,8 @@ static UIImage *RCTGetSolidBorderImage( return image; } -// Currently, the dashed / dotted implementation only supports a single colour + -// single width, as that's currently required and supported on Android. +// Currently, the dashed / dotted implementation only supports a single colour, +// as that's currently required and supported on Android. // // Supporting individual widths + colours on each side is possible by modifying // the current implementation. The idea is that we will draw four different lines @@ -486,12 +491,12 @@ static UIImage *RCTGetDashedOrDottedBorderImage( { NSCParameterAssert(borderStyle == RCTBorderStyleDashed || borderStyle == RCTBorderStyleDotted); - if (!RCTBorderColorsAreEqual(borderColors) || !RCTBorderInsetsAreEqual(borderInsets)) { + if (!RCTBorderColorsAreEqual(borderColors)) { RCTLogWarn(@"Unsupported dashed / dotted border style"); return nil; } - const CGFloat lineWidth = borderInsets.top; + const CGFloat lineWidth = RCTMaxBorderInset(borderInsets); if (lineWidth <= 0.0) { return nil; } @@ -519,6 +524,34 @@ static UIImage *RCTGetDashedOrDottedBorderImage( CGPathRef path = RCTPathCreateWithRoundedRect(pathRect, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL, NO); + if (!RCTBorderInsetsAreEqual(borderInsets)) { + CGContextSaveGState(context); + { + // Create a path representing the full rect + CGMutablePathRef outerPath = CGPathCreateMutable(); + CGPathAddRect(outerPath, NULL, rect); + + CGRect insetRect = CGRectMake( + rect.origin.x + borderInsets.left, + rect.origin.y + borderInsets.top, + rect.size.width - borderInsets.left - borderInsets.right, + rect.size.height - borderInsets.top - borderInsets.bottom); + + // The padding edge (inner border) radius is the outer border radius minus the corresponding border thickness + CGPathRef innerRoundedRect = + RCTPathCreateWithRoundedRect(insetRect, RCTGetCornerInsets(cornerRadii, borderInsets), NULL, NO); + + // Add both paths to outerPath + CGPathAddPath(outerPath, NULL, innerRoundedRect); + + // Clip using even-odd + CGContextAddPath(context, outerPath); + CGContextEOClip(context); + CGPathRelease(outerPath); + CGPathRelease(innerRoundedRect); + } + } + CGFloat dashLengths[2]; dashLengths[0] = dashLengths[1] = (borderStyle == RCTBorderStyleDashed ? 3 : 1) * lineWidth;