From 70962ef3ed06a76a96cb2e72c374dc028628c829 Mon Sep 17 00:00:00 2001 From: Alexander Klotz Date: Thu, 12 Jun 2025 15:31:46 -0700 Subject: [PATCH] Added support for multiple widths with dashed and dotted borders on iOS (#51770) Summary: This change allows for dashed and dotted borders to have different widths for each of the sides on iOS. This issue was described in https://github.com/facebook/react-native/issues/51658. This allows for better dashed lines and moves the implementation of borders closer to how it is handled on web/android. Resolves https://github.com/facebook/react-native/issues/51658 (related https://github.com/facebook/react-native/issues/39088) ## Changelog: [IOS] [ADDED] - Add support for different borderWidths Pull Request resolved: https://github.com/facebook/react-native/pull/51770 Test Plan: - yarn test - yarn lint Reviewed By: NickGerleman Differential Revision: D76145887 Pulled By: jorge-cab fbshipit-source-id: 3716e84799b44d2ff0994cc673a2172ee85bd9e6 --- .../React/Views/RCTBorderDrawing.h | 5 +++ .../React/Views/RCTBorderDrawing.m | 41 +++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) 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;