react-native-shared-element icon indicating copy to clipboard operation
react-native-shared-element copied to clipboard

Incompatible with react-native 0.76 on iOS due to border radii

Open jparkrr opened this issue 1 year ago • 6 comments

There were some changes to RCTCornerRadii in 0.76: https://github.com/facebook/react-native/pull/46009. This causes this package to error during compilation for iOS.

I made a quick patch with patch-package. I've done some brief tests and it seems to work. This assumes all your border radii are symmetric, so a real PR would need to take asymmetric values into account. Also this patch is not backwards compatible.

patches/react-native-shared-element+0.8.9.patch:

diff --git a/node_modules/react-native-shared-element/ios/RNSharedElementCornerRadii.m b/node_modules/react-native-shared-element/ios/RNSharedElementCornerRadii.m
index 5060067..413281a 100644
--- a/node_modules/react-native-shared-element/ios/RNSharedElementCornerRadii.m
+++ b/node_modules/react-native-shared-element/ios/RNSharedElementCornerRadii.m
@@ -69,8 +69,8 @@ static CGFloat RNSharedElementDefaultIfNegativeTo(CGFloat defaultValue, CGFloat
   CALayer *mask = nil;
   CGFloat cornerRadius = 0;
   
-  if (RCTCornerRadiiAreEqual(radii)) {
-    cornerRadius = radii.topLeft;
+  if (RCTCornerRadiiAreEqualAndSymmetrical(radii)) {
+    cornerRadius = radii.topLeftHorizontal;
   } else {
     CAShapeLayer *shapeLayer = [CAShapeLayer layer];
     RCTCornerInsets cornerInsets = RCTGetCornerInsets(radii, UIEdgeInsetsZero);
@@ -121,36 +121,52 @@ static CGFloat RNSharedElementDefaultIfNegativeTo(CGFloat defaultValue, CGFloat
     const CGFloat directionAwareBottomLeftRadius = isRTL ? bottomEndRadius : bottomStartRadius;
     const CGFloat directionAwareBottomRightRadius = isRTL ? bottomStartRadius : bottomEndRadius;
     
-    result.topLeft = RNSharedElementDefaultIfNegativeTo(radius, directionAwareTopLeftRadius);
-    result.topRight = RNSharedElementDefaultIfNegativeTo(radius, directionAwareTopRightRadius);
-    result.bottomLeft = RNSharedElementDefaultIfNegativeTo(radius, directionAwareBottomLeftRadius);
-    result.bottomRight = RNSharedElementDefaultIfNegativeTo(radius, directionAwareBottomRightRadius);
+    result.topLeftHorizontal = RNSharedElementDefaultIfNegativeTo(radius, directionAwareTopLeftRadius);
+    result.topRightHorizontal = RNSharedElementDefaultIfNegativeTo(radius, directionAwareTopRightRadius);
+    result.bottomLeftHorizontal = RNSharedElementDefaultIfNegativeTo(radius, directionAwareBottomLeftRadius);
+    result.bottomRightHorizontal = RNSharedElementDefaultIfNegativeTo(radius, directionAwareBottomRightRadius);
+    result.topLeftVertical = RNSharedElementDefaultIfNegativeTo(radius, directionAwareTopLeftRadius);
+    result.topRightVertical = RNSharedElementDefaultIfNegativeTo(radius, directionAwareTopRightRadius);
+    result.bottomLeftVertical = RNSharedElementDefaultIfNegativeTo(radius, directionAwareBottomLeftRadius);
+    result.bottomRightVertical = RNSharedElementDefaultIfNegativeTo(radius, directionAwareBottomRightRadius);
   } else {
     const CGFloat directionAwareTopLeftRadius = isRTL ? _radii[RNSharedElementCornerTopEnd] : _radii[RNSharedElementCornerTopStart];
     const CGFloat directionAwareTopRightRadius = isRTL ? _radii[RNSharedElementCornerTopStart] : _radii[RNSharedElementCornerTopEnd];
     const CGFloat directionAwareBottomLeftRadius = isRTL ? _radii[RNSharedElementCornerBottomEnd] : _radii[RNSharedElementCornerBottomStart];
     const CGFloat directionAwareBottomRightRadius = isRTL ? _radii[RNSharedElementCornerBottomStart] : _radii[RNSharedElementCornerBottomEnd];
     
-    result.topLeft =
+    result.topLeftHorizontal =
     RNSharedElementDefaultIfNegativeTo(radius, RNSharedElementDefaultIfNegativeTo(_radii[RNSharedElementCornerTopLeft], directionAwareTopLeftRadius));
-    result.topRight =
+    result.topRightHorizontal =
     RNSharedElementDefaultIfNegativeTo(radius, RNSharedElementDefaultIfNegativeTo(_radii[RNSharedElementCornerTopRight], directionAwareTopRightRadius));
-    result.bottomLeft =
+    result.bottomLeftHorizontal =
     RNSharedElementDefaultIfNegativeTo(radius, RNSharedElementDefaultIfNegativeTo(_radii[RNSharedElementCornerBottomLeft], directionAwareBottomLeftRadius));
-    result.bottomRight = RNSharedElementDefaultIfNegativeTo(
+    result.bottomRightHorizontal = RNSharedElementDefaultIfNegativeTo(
+                                                            radius, RNSharedElementDefaultIfNegativeTo(_radii[RNSharedElementCornerBottomRight], directionAwareBottomRightRadius));
+    result.topLeftVertical =
+    RNSharedElementDefaultIfNegativeTo(radius, RNSharedElementDefaultIfNegativeTo(_radii[RNSharedElementCornerTopLeft], directionAwareTopLeftRadius));
+    result.topRightVertical =
+    RNSharedElementDefaultIfNegativeTo(radius, RNSharedElementDefaultIfNegativeTo(_radii[RNSharedElementCornerTopRight], directionAwareTopRightRadius));
+    result.bottomLeftVertical =
+    RNSharedElementDefaultIfNegativeTo(radius, RNSharedElementDefaultIfNegativeTo(_radii[RNSharedElementCornerBottomLeft], directionAwareBottomLeftRadius));
+    result.bottomRightVertical = RNSharedElementDefaultIfNegativeTo(
                                                             radius, RNSharedElementDefaultIfNegativeTo(_radii[RNSharedElementCornerBottomRight], directionAwareBottomRightRadius));
   }
   
   // Get scale factors required to prevent radii from overlapping
-  const CGFloat topScaleFactor = RCTZeroIfNaN(MIN(1, bounds.size.width / (result.topLeft + result.topRight)));
-  const CGFloat bottomScaleFactor = RCTZeroIfNaN(MIN(1, bounds.size.width / (result.bottomLeft + result.bottomRight)));
-  const CGFloat rightScaleFactor = RCTZeroIfNaN(MIN(1, bounds.size.height / (result.topRight + result.bottomRight)));
-  const CGFloat leftScaleFactor = RCTZeroIfNaN(MIN(1, bounds.size.height / (result.topLeft + result.bottomLeft)));
-  
-  result.topLeft *= MIN(topScaleFactor, leftScaleFactor);
-  result.topRight *= MIN(topScaleFactor, rightScaleFactor);
-  result.bottomLeft *= MIN(bottomScaleFactor, leftScaleFactor);
-  result.bottomRight *= MIN(bottomScaleFactor, rightScaleFactor);
+  const CGFloat topScaleFactor = RCTZeroIfNaN(MIN(1, bounds.size.width / (result.topLeftHorizontal + result.topRightHorizontal)));
+  const CGFloat bottomScaleFactor = RCTZeroIfNaN(MIN(1, bounds.size.width / (result.bottomLeftHorizontal + result.bottomRightHorizontal)));
+  const CGFloat rightScaleFactor = RCTZeroIfNaN(MIN(1, bounds.size.height / (result.topRightHorizontal + result.bottomRightHorizontal)));
+  const CGFloat leftScaleFactor = RCTZeroIfNaN(MIN(1, bounds.size.height / (result.topLeftHorizontal + result.bottomLeftHorizontal)));
+  
+  result.topLeftHorizontal *= MIN(topScaleFactor, leftScaleFactor);
+  result.topRightHorizontal *= MIN(topScaleFactor, rightScaleFactor);
+  result.bottomLeftHorizontal *= MIN(bottomScaleFactor, leftScaleFactor);
+  result.bottomRightHorizontal *= MIN(bottomScaleFactor, rightScaleFactor);
+  result.topLeftVertical *= MIN(topScaleFactor, leftScaleFactor);
+  result.topRightVertical *= MIN(topScaleFactor, rightScaleFactor);
+  result.bottomLeftVertical *= MIN(bottomScaleFactor, leftScaleFactor);
+  result.bottomRightVertical *= MIN(bottomScaleFactor, rightScaleFactor);
   
   _cachedBounds = bounds;
   _cachedRadii = result;
diff --git a/node_modules/react-native-shared-element/ios/RNSharedElementStyle.m b/node_modules/react-native-shared-element/ios/RNSharedElementStyle.m
index 9f65b9d..6ae25d8 100644
--- a/node_modules/react-native-shared-element/ios/RNSharedElementStyle.m
+++ b/node_modules/react-native-shared-element/ios/RNSharedElementStyle.m
@@ -112,10 +112,10 @@
   CGRect radiiRect = CGRectMake(0, 0, 1000000, 1000000);
   RCTCornerRadii radii1 = [style1.cornerRadii radiiForBounds:radiiRect];
   RCTCornerRadii radii2 = [style2.cornerRadii radiiForBounds:radiiRect];
-  [style.cornerRadii setRadius:radii1.topLeft + ((radii2.topLeft - radii1.topLeft) * position) corner:RNSharedElementCornerTopLeft];
-  [style.cornerRadii setRadius:radii1.topRight + ((radii2.topRight - radii1.topRight) * position) corner:RNSharedElementCornerTopRight];
-  [style.cornerRadii setRadius:radii1.bottomLeft + ((radii2.bottomLeft - radii1.bottomLeft) * position) corner:RNSharedElementCornerBottomLeft];
-  [style.cornerRadii setRadius:radii1.bottomRight + ((radii2.bottomRight - radii1.bottomRight) * position) corner:RNSharedElementCornerBottomRight];
+  [style.cornerRadii setRadius:radii1.topLeftHorizontal + ((radii2.topLeftHorizontal - radii1.topLeftHorizontal) * position) corner:RNSharedElementCornerTopLeft];
+  [style.cornerRadii setRadius:radii1.topRightHorizontal + ((radii2.topRightHorizontal - radii1.topRightHorizontal) * position) corner:RNSharedElementCornerTopRight];
+  [style.cornerRadii setRadius:radii1.bottomLeftHorizontal + ((radii2.bottomLeftHorizontal - radii1.bottomLeftHorizontal) * position) corner:RNSharedElementCornerBottomLeft];
+  [style.cornerRadii setRadius:radii1.bottomRightHorizontal + ((radii2.bottomRightHorizontal - radii1.bottomRightHorizontal) * position) corner:RNSharedElementCornerBottomRight];
   
   style.borderWidth = style1.borderWidth + ((style2.borderWidth - style1.borderWidth) * position);
   style.borderColor = [RNSharedElementStyle getInterpolatedColor:style1.borderColor color2:style2.borderColor position:position];
diff --git a/node_modules/react-native-shared-element/ios/RNSharedElementTransition.m b/node_modules/react-native-shared-element/ios/RNSharedElementTransition.m
index 97de533..dbd1d66 100644
--- a/node_modules/react-native-shared-element/ios/RNSharedElementTransition.m
+++ b/node_modules/react-native-shared-element/ios/RNSharedElementTransition.m
@@ -388,10 +388,10 @@
     },
     @"contentType": item.content ? item.content.typeName : @"none",
     @"style": @{
-        @"borderTopLeftRadius": @(cornerRadii.topLeft),
-        @"borderTopRightRadius": @(cornerRadii.topRight),
-        @"borderBottomLeftRadius": @(cornerRadii.bottomLeft),
-        @"borderBotomRightRadius": @(cornerRadii.bottomRight)
+        @"borderTopLeftRadius": @(cornerRadii.topLeftHorizontal),
+        @"borderTopRightRadius": @(cornerRadii.topRightHorizontal),
+        @"borderBottomLeftRadius": @(cornerRadii.bottomLeftHorizontal),
+        @"borderBotomRightRadius": @(cornerRadii.bottomRightHorizontal)
     }
   };
   self.onMeasureNode(eventData);

jparkrr avatar Dec 04 '24 20:12 jparkrr

Hey! Thanks for opening the issue. The issue doesn't seem to contain a link to a repro (a snack.expo.dev link or link to a GitHub repo under your username).

Can you provide a minimal repro which demonstrates the issue? A repro will help us debug the issue faster. Please try to keep the repro as small as possible and make sure that we can run it without additional setup.

github-actions[bot] avatar Dec 04 '24 20:12 github-actions[bot]

react-native-shared-element+0.8.9.patch

Many thanks for providing a patch for this issue. Fixed my build problem and saved me time. Kudos!

pep108 avatar Dec 09 '24 16:12 pep108

Thank you @jparkrr for providing a patch for this 🙏 Will have look on incorporating this in a new release 👍

IjzerenHein avatar Dec 12 '24 07:12 IjzerenHein

Build just errored out for the same reason. Thanks for the patch and looking forward to having it in a new release soon.

sherikovic avatar Dec 13 '24 19:12 sherikovic

I've released a pre-release which should solve the compatibility issues on both iOS and Android. On iOS I didn't see any issues after the changes, on Android there seems to be a subtle rendering mismatch in the border rendering, but it should work though. I've published it as a pre-release (v0.9.0-rc0) to NPM, which you can install using yarn add react-native-shared-element@next (or whichever package manager you are using)

Please let me know if that resolves the problem for you 🙏

IjzerenHein avatar Jan 21 '25 15:01 IjzerenHein

@IjzerenHein any plans to release a stable version with the fixes?

1880akshay avatar Mar 27 '25 09:03 1880akshay