Added border curve style prop ("Squircle" effect - iOS only) (#33783)
Summary: <!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? --> NOTE: PR is based on https://github.com/facebook/react-native/pull/32017 which went stale for quite a long time but can now safely be closed ![](https://preview.redd.it/nuvl4746ys471.png?width=960&crop=smart&auto=webp&s=084a517a645364ac246b70b7fa8e0f2470cc7af3) Since iOS 13+, it is possible to change the corner curve property on iOS in order to smoothen border radius and make it more "rounded" (also called "squircle") Here's an [article](https://medium.com/arthurofbabylon/a-smooth-corner-radius-in-ios-54b80aa2d372) explaining in details what it is. This property is also built in figma, but currently there is no way to implement this directly with react-native despite it being available natively on iOS. Many open source react-native libraries were created in order to simulate this behaviour: [react-native-super-ellipse-mask](https://github.com/everdrone/react-native-super-ellipse-mask) [react-native-squircle-view](https://github.com/everdrone/react-native-squircle-view) [react-native-figma-squircle](https://github.com/tienphaw/react-native-figma-squircle) But they rely on creating an SVG shape with the smoothed corners and masking the view behind. This makes it not very performant (flickering on mounting was a common side-effect) This PR aims at implementing the property natively. PR for the docs update: https://github.com/facebook/react-native-website/pull/2785 ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> [iOS] [Added] - Added `borderCurve` style prop for smooth border radius (squircle effect) Pull Request resolved: https://github.com/facebook/react-native/pull/33783 Test Plan: We used the RNTester app and added an example with `cornerCurve ` set to `'continuous'` (only on iOS). As the difference is quite subtle, we also made some more tests to better illustrate the difference (these are not in the RN-tester app): ![IMG_0810](https://user-images.githubusercontent.com/19872411/133893536-26207c53-aade-4583-9eef-7a1739b6907b.PNG) We overlapped two views with `position: absolute`, the one in the background has a red background and has `cornerRadius` set to `false`, and the one in the foreground is set to `true`. We can clearly see where the borders differs on the corners. Reviewed By: sammy-SC Differential Revision: D37883631 Pulled By: cipolleschi fbshipit-source-id: 09f06de9628fa326323eba63875de30102c4a59e
This commit is contained in:
Родитель
64528e5faa
Коммит
8993ffc82e
1
BUCK
1
BUCK
|
@ -248,6 +248,7 @@ REACT_PUBLIC_HEADERS = {
|
|||
"React/RCTAnimationType.h": RCTVIEWS_PATH + "RCTAnimationType.h",
|
||||
"React/RCTAssert.h": RCTBASE_PATH + "RCTAssert.h",
|
||||
"React/RCTAutoInsetsProtocol.h": RCTVIEWS_PATH + "RCTAutoInsetsProtocol.h",
|
||||
"React/RCTBorderCurve.h": RCTVIEWS_PATH + "RCTBorderCurve.h",
|
||||
"React/RCTBorderDrawing.h": RCTVIEWS_PATH + "RCTBorderDrawing.h",
|
||||
"React/RCTBorderStyle.h": RCTVIEWS_PATH + "RCTBorderStyle.h",
|
||||
"React/RCTBridge+Private.h": RCTBASE_PATH + "RCTBridge+Private.h",
|
||||
|
|
|
@ -98,6 +98,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
|
|||
borderBottomRightRadius: true,
|
||||
borderBottomStartRadius: true,
|
||||
borderColor: colorAttributes,
|
||||
borderCurve: true,
|
||||
borderEndColor: colorAttributes,
|
||||
borderLeftColor: colorAttributes,
|
||||
borderRadius: true,
|
||||
|
|
|
@ -193,6 +193,7 @@ const validAttributesForNonEventProps = {
|
|||
removeClippedSubviews: true,
|
||||
borderRadius: true,
|
||||
borderColor: {process: require('../StyleSheet/processColor')},
|
||||
borderCurve: true,
|
||||
borderWidth: true,
|
||||
borderStyle: true,
|
||||
hitSlop: {diff: require('../Utilities/differ/insetsDiffer')},
|
||||
|
|
|
@ -532,6 +532,7 @@ export type ____ViewStyle_InternalCore = $ReadOnly<{
|
|||
backfaceVisibility?: 'visible' | 'hidden',
|
||||
backgroundColor?: ____ColorValue_Internal,
|
||||
borderColor?: ____ColorValue_Internal,
|
||||
borderCurve?: 'circular' | 'continuous',
|
||||
borderBottomColor?: ____ColorValue_Internal,
|
||||
borderEndColor?: ____ColorValue_Internal,
|
||||
borderLeftColor?: ____ColorValue_Internal,
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <React/RCTAnimationType.h>
|
||||
#import <React/RCTBorderCurve.h>
|
||||
#import <React/RCTBorderStyle.h>
|
||||
#import <React/RCTDefines.h>
|
||||
#import <React/RCTLog.h>
|
||||
|
@ -130,6 +131,7 @@ typedef BOOL css_backface_visibility_t;
|
|||
+ (RCTPointerEvents)RCTPointerEvents:(id)json;
|
||||
+ (RCTAnimationType)RCTAnimationType:(id)json;
|
||||
+ (RCTBorderStyle)RCTBorderStyle:(id)json;
|
||||
+ (RCTBorderCurve)RCTBorderCurve:(id)json;
|
||||
+ (RCTTextDecorationLineType)RCTTextDecorationLineType:(id)json;
|
||||
|
||||
@end
|
||||
|
|
|
@ -345,6 +345,15 @@ RCT_ENUM_CONVERTER(
|
|||
RCTBorderStyleSolid,
|
||||
integerValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(
|
||||
RCTBorderCurve,
|
||||
(@{
|
||||
@"circular" : @(RCTBorderCurveCircular),
|
||||
@"continuous" : @(RCTBorderCurveContinuous),
|
||||
}),
|
||||
RCTBorderCurveCircular,
|
||||
integerValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(
|
||||
RCTTextDecorationLineType,
|
||||
(@{
|
||||
|
|
|
@ -516,6 +516,18 @@ static void RCTReleaseRCTBorderColors(RCTBorderColors borderColors)
|
|||
CGColorRelease(borderColors.right);
|
||||
}
|
||||
|
||||
static CALayerCornerCurve CornerCurveFromBorderCurve(BorderCurve borderCurve)
|
||||
{
|
||||
// The constants are available only starting from iOS 13
|
||||
// CALayerCornerCurve is a typealias on NSString *
|
||||
switch (borderCurve) {
|
||||
case BorderCurve::Continuous:
|
||||
return @"continuous"; // kCACornerCurveContinuous;
|
||||
case BorderCurve::Circular:
|
||||
return @"circular"; // kCACornerCurveCircular;
|
||||
}
|
||||
}
|
||||
|
||||
static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle)
|
||||
{
|
||||
switch (borderStyle) {
|
||||
|
@ -580,6 +592,9 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle)
|
|||
layer.borderColor = borderColor;
|
||||
CGColorRelease(borderColor);
|
||||
layer.cornerRadius = (CGFloat)borderMetrics.borderRadii.topLeft;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
layer.cornerCurve = CornerCurveFromBorderCurve(borderMetrics.borderCurves.topLeft);
|
||||
}
|
||||
layer.backgroundColor = _backgroundColor.CGColor;
|
||||
} else {
|
||||
if (!_borderLayer) {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, RCTBorderCurve) {
|
||||
RCTBorderCurveContinuous = 0,
|
||||
RCTBorderCurveCircular,
|
||||
};
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <React/RCTBorderCurve.h>
|
||||
#import <React/RCTBorderStyle.h>
|
||||
#import <React/RCTComponent.h>
|
||||
#import <React/RCTPointerEvents.h>
|
||||
|
@ -93,6 +94,11 @@ extern const UIAccessibilityTraits SwitchAccessibilityTrait;
|
|||
@property (nonatomic, assign) CGFloat borderEndWidth;
|
||||
@property (nonatomic, assign) CGFloat borderWidth;
|
||||
|
||||
/**
|
||||
* Border curve.
|
||||
*/
|
||||
@property (nonatomic, assign) RCTBorderCurve borderCurve;
|
||||
|
||||
/**
|
||||
* Border styles.
|
||||
*/
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#import <React/RCTMockDef.h>
|
||||
|
||||
#import "RCTAutoInsetsProtocol.h"
|
||||
#import "RCTBorderCurve.h"
|
||||
#import "RCTBorderDrawing.h"
|
||||
#import "RCTI18nUtil.h"
|
||||
#import "RCTLog.h"
|
||||
|
@ -126,6 +127,7 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
|||
_borderBottomRightRadius = -1;
|
||||
_borderBottomStartRadius = -1;
|
||||
_borderBottomEndRadius = -1;
|
||||
_borderCurve = RCTBorderCurveCircular;
|
||||
_borderStyle = RCTBorderStyleSolid;
|
||||
_hitTestEdgeInsets = UIEdgeInsetsZero;
|
||||
|
||||
|
@ -945,6 +947,20 @@ setBorderColor() setBorderColor(Top) setBorderColor(Right) setBorderColor(Bottom
|
|||
setBorderRadius(TopEnd) setBorderRadius(BottomLeft) setBorderRadius(BottomRight)
|
||||
setBorderRadius(BottomStart) setBorderRadius(BottomEnd)
|
||||
|
||||
#pragma mark - Border Curve
|
||||
|
||||
#define setBorderCurve(side) \
|
||||
-(void)setBorder##side##Curve : (RCTBorderCurve)curve \
|
||||
{ \
|
||||
if (_border##side##Curve == curve) { \
|
||||
return; \
|
||||
} \
|
||||
_border##side##Curve = curve; \
|
||||
[self.layer setNeedsDisplay]; \
|
||||
}
|
||||
|
||||
setBorderCurve()
|
||||
|
||||
#pragma mark - Border Style
|
||||
|
||||
#define setBorderStyle(side) \
|
||||
|
@ -957,6 +973,6 @@ setBorderColor() setBorderColor(Top) setBorderColor(Right) setBorderColor(Bottom
|
|||
[self.layer setNeedsDisplay]; \
|
||||
}
|
||||
|
||||
setBorderStyle()
|
||||
setBorderStyle()
|
||||
|
||||
@end
|
||||
@end
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#import "RCTViewManager.h"
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBorderCurve.h"
|
||||
#import "RCTBorderStyle.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert+Transform.h"
|
||||
|
@ -264,6 +265,19 @@ RCT_CUSTOM_VIEW_PROPERTY(removeClippedSubviews, BOOL, RCTView)
|
|||
view.removeClippedSubviews = json ? [RCTConvert BOOL:json] : defaultView.removeClippedSubviews;
|
||||
}
|
||||
}
|
||||
RCT_CUSTOM_VIEW_PROPERTY(borderCurve, RCTBorderCurve, RCTView)
|
||||
{
|
||||
if (@available(iOS 13.0, *)) {
|
||||
switch ([RCTConvert RCTBorderCurve:json]) {
|
||||
case RCTBorderCurveContinuous:
|
||||
view.layer.cornerCurve = kCACornerCurveContinuous;
|
||||
break;
|
||||
case RCTBorderCurveCircular:
|
||||
view.layer.cornerCurve = kCACornerCurveCircular;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
RCT_CUSTOM_VIEW_PROPERTY(borderRadius, CGFloat, RCTView)
|
||||
{
|
||||
if ([view respondsToSelector:@selector(setBorderRadius:)]) {
|
||||
|
|
|
@ -67,6 +67,15 @@ ViewProps::ViewProps(
|
|||
"Color",
|
||||
sourceProps.borderColors,
|
||||
{})),
|
||||
borderCurves(
|
||||
Props::enablePropIteratorSetter ? sourceProps.borderCurves
|
||||
: convertRawProp(
|
||||
context,
|
||||
rawProps,
|
||||
"border",
|
||||
"Curve",
|
||||
sourceProps.borderCurves,
|
||||
{})),
|
||||
borderStyles(
|
||||
Props::enablePropIteratorSetter ? sourceProps.borderStyles
|
||||
: convertRawProp(
|
||||
|
@ -412,6 +421,7 @@ BorderMetrics ViewProps::resolveBorderMetrics(
|
|||
/* .borderWidths = */ borderWidths.resolve(isRTL, 0),
|
||||
/* .borderRadii = */
|
||||
ensureNoOverlap(borderRadii.resolve(isRTL, 0), layoutMetrics.frame.size),
|
||||
/* .borderCurves = */ borderCurves.resolve(isRTL, BorderCurve::Circular),
|
||||
/* .borderStyles = */ borderStyles.resolve(isRTL, BorderStyle::Solid),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ class ViewProps : public YogaStylableProps, public AccessibilityProps {
|
|||
// Borders
|
||||
CascadedBorderRadii borderRadii{};
|
||||
CascadedBorderColors borderColors{};
|
||||
CascadedBorderCurves borderCurves{};
|
||||
CascadedBorderStyles borderStyles{};
|
||||
|
||||
// Shadow
|
||||
|
|
|
@ -563,6 +563,24 @@ inline void fromRawValue(
|
|||
react_native_assert(false);
|
||||
}
|
||||
|
||||
inline void fromRawValue(
|
||||
const PropsParserContext &context,
|
||||
const RawValue &value,
|
||||
BorderCurve &result) {
|
||||
react_native_assert(value.hasType<std::string>());
|
||||
auto stringValue = (std::string)value;
|
||||
if (stringValue == "circular") {
|
||||
result = BorderCurve::Circular;
|
||||
return;
|
||||
}
|
||||
if (stringValue == "continuous") {
|
||||
result = BorderCurve::Continuous;
|
||||
return;
|
||||
}
|
||||
LOG(FATAL) << "Could not parse BorderCurve:" << stringValue;
|
||||
react_native_assert(false);
|
||||
}
|
||||
|
||||
inline void fromRawValue(
|
||||
const PropsParserContext &context,
|
||||
const RawValue &value,
|
||||
|
|
|
@ -77,6 +77,8 @@ inline static bool operator!=(ViewEvents const &lhs, ViewEvents const &rhs) {
|
|||
|
||||
enum class BackfaceVisibility { Auto, Visible, Hidden };
|
||||
|
||||
enum class BorderCurve { Circular, Continuous };
|
||||
|
||||
enum class BorderStyle { Solid, Dotted, Dashed };
|
||||
|
||||
template <typename T>
|
||||
|
@ -202,11 +204,13 @@ struct CascadedRectangleCorners {
|
|||
};
|
||||
|
||||
using BorderWidths = RectangleEdges<Float>;
|
||||
using BorderCurves = RectangleCorners<BorderCurve>;
|
||||
using BorderStyles = RectangleEdges<BorderStyle>;
|
||||
using BorderColors = RectangleEdges<SharedColor>;
|
||||
using BorderRadii = RectangleCorners<Float>;
|
||||
|
||||
using CascadedBorderWidths = CascadedRectangleEdges<Float>;
|
||||
using CascadedBorderCurves = CascadedRectangleCorners<BorderCurve>;
|
||||
using CascadedBorderStyles = CascadedRectangleEdges<BorderStyle>;
|
||||
using CascadedBorderColors = CascadedRectangleEdges<SharedColor>;
|
||||
using CascadedBorderRadii = CascadedRectangleCorners<Float>;
|
||||
|
@ -215,6 +219,7 @@ struct BorderMetrics {
|
|||
BorderColors borderColors{};
|
||||
BorderWidths borderWidths{};
|
||||
BorderRadii borderRadii{};
|
||||
BorderCurves borderCurves{};
|
||||
BorderStyles borderStyles{};
|
||||
|
||||
bool operator==(const BorderMetrics &rhs) const {
|
||||
|
@ -222,11 +227,13 @@ struct BorderMetrics {
|
|||
this->borderColors,
|
||||
this->borderWidths,
|
||||
this->borderRadii,
|
||||
this->borderCurves,
|
||||
this->borderStyles) ==
|
||||
std::tie(
|
||||
rhs.borderColors,
|
||||
rhs.borderWidths,
|
||||
rhs.borderRadii,
|
||||
rhs.borderCurves,
|
||||
rhs.borderStyles);
|
||||
}
|
||||
|
||||
|
|
|
@ -753,7 +753,7 @@ DEPENDENCIES:
|
|||
- RCTRequired (from `../../Libraries/RCTRequired`)
|
||||
- RCTTypeSafety (from `../../Libraries/TypeSafety`)
|
||||
- React (from `../../`)
|
||||
- React-bridging (from `../../ReactCommon/react/bridging`)
|
||||
- React-bridging (from `../../ReactCommon`)
|
||||
- React-callinvoker (from `../../ReactCommon/callinvoker`)
|
||||
- React-Codegen (from `build/generated/ios`)
|
||||
- React-Core (from `../../`)
|
||||
|
@ -826,7 +826,7 @@ EXTERNAL SOURCES:
|
|||
React:
|
||||
:path: "../../"
|
||||
React-bridging:
|
||||
:path: "../../ReactCommon/react/bridging"
|
||||
:path: "../../ReactCommon"
|
||||
React-callinvoker:
|
||||
:path: "../../ReactCommon/callinvoker"
|
||||
React-Codegen:
|
||||
|
@ -905,11 +905,11 @@ SPEC CHECKSUMS:
|
|||
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
|
||||
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
|
||||
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
|
||||
RCT-Folly: 9638863070ed4e7b2be5e91385745a0ad741e9c1
|
||||
RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda
|
||||
RCTRequired: 1c8808cf84569265784a6c33984bbb506ada8c6e
|
||||
RCTTypeSafety: b6dcb5036a808864ee8cad66ca15f263c24661cc
|
||||
React: 8d809d414723bb5763093ddec7658066a21ccabc
|
||||
React-bridging: 3ba3efbd3a2d7d99aad5658b8e48b1c134c16ecf
|
||||
React-bridging: cc10a051eff1f03306a1d7659593d8aac3242bc3
|
||||
React-callinvoker: 5f16202ad4e45f0607b1fae0f6955a8f7c87eef1
|
||||
React-Codegen: 5adf19af97eb37a7d441c040521191e446255086
|
||||
React-Core: 0cfb25c65d4dcb856b1807fe44a1ebe5e7ec9749
|
||||
|
@ -936,12 +936,12 @@ SPEC CHECKSUMS:
|
|||
React-RCTVibration: 0386f50996a153b3f39cecbe7d139763ac9a9fdf
|
||||
React-rncore: 6daa27c74047a9e13ce3412b99660274a5780603
|
||||
React-runtimeexecutor: 97dca9247f4d3cfe0733384b189c6930fbd402b7
|
||||
ReactCommon: a34f02c7251e6725e744167b9381d5dd9d016591
|
||||
ReactCommon: 6cef8ed13ee2a9d7d4cf9660dbe6dd2ea6ba7104
|
||||
ScreenshotManager: 71d047abd38a77310985b87f8136b620c5c61e88
|
||||
SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608
|
||||
Yoga: 1b1a12ff3d86a10565ea7cbe057d42f5e5fb2a07
|
||||
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
|
||||
|
||||
PODFILE CHECKSUM: e067e04e7697dc3cd3d3fbd7556f77c6eccf8075
|
||||
PODFILE CHECKSUM: 54d9bd86f3c8151531bd4da1d3ba2e2e1f9a6ca9
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
|
|
|
@ -17,6 +17,7 @@ const {
|
|||
Text,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
Platform,
|
||||
} = require('react-native');
|
||||
|
||||
class ViewBorderStyleExample extends React.Component<
|
||||
|
@ -360,12 +361,29 @@ exports.examples = [
|
|||
title: 'Border Radius',
|
||||
render(): React.Node {
|
||||
return (
|
||||
<View style={{borderWidth: 0.5, borderRadius: 5, padding: 5}}>
|
||||
<Text style={{fontSize: 11}}>
|
||||
Too much use of `borderRadius` (especially large radii) on anything
|
||||
which is scrolling may result in dropped frames. Use sparingly.
|
||||
</Text>
|
||||
</View>
|
||||
<>
|
||||
<View style={{borderWidth: 0.5, borderRadius: 5, padding: 5}}>
|
||||
<Text style={{fontSize: 11}}>
|
||||
Too much use of `borderRadius` (especially large radii) on
|
||||
anything which is scrolling may result in dropped frames. Use
|
||||
sparingly.
|
||||
</Text>
|
||||
</View>
|
||||
{Platform.OS === 'ios' && (
|
||||
<View
|
||||
style={{
|
||||
borderRadius: 20,
|
||||
padding: 8,
|
||||
marginTop: 12,
|
||||
backgroundColor: '#527FE4',
|
||||
borderCurve: 'continuous',
|
||||
}}>
|
||||
<Text style={{fontSize: 16, color: 'white'}}>
|
||||
View with continuous border curve
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
|
Загрузка…
Ссылка в новой задаче