[React Native] Fix view clipping when border radius is set
This commit is contained in:
parent
9062bda79b
commit
81401064e5
|
@ -80,6 +80,13 @@ var styles = StyleSheet.create({
|
||||||
|
|
||||||
borderTopLeftRadius: 100,
|
borderTopLeftRadius: 100,
|
||||||
},
|
},
|
||||||
|
border7: {
|
||||||
|
borderRadius: 20,
|
||||||
|
},
|
||||||
|
border7_inner: {
|
||||||
|
backgroundColor: 'blue',
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
exports.title = 'Border';
|
exports.title = 'Border';
|
||||||
|
@ -134,4 +141,15 @@ exports.examples = [
|
||||||
return <View style={[styles.box, styles.border6]} />;
|
return <View style={[styles.box, styles.border6]} />;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Custom Borders',
|
||||||
|
description: 'borderRadius & clipping',
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View style={[styles.box, styles.border7]}>
|
||||||
|
<View style={styles.border7_inner} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
#import "RCTUtils.h"
|
#import "RCTUtils.h"
|
||||||
#import "UIView+React.h"
|
#import "UIView+React.h"
|
||||||
|
|
||||||
|
static const CGFloat RCTViewBorderThreshold = 0.001;
|
||||||
|
|
||||||
static UIView *RCTViewHitTest(UIView *view, CGPoint point, UIEvent *event)
|
static UIView *RCTViewHitTest(UIView *view, CGPoint point, UIEvent *event)
|
||||||
{
|
{
|
||||||
for (UIView *subview in [view.subviews reverseObjectEnumerator]) {
|
for (UIView *subview in [view.subviews reverseObjectEnumerator]) {
|
||||||
|
@ -436,19 +438,21 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
||||||
if ([_backgroundColor isEqual:backgroundColor]) {
|
if ([_backgroundColor isEqual:backgroundColor]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_backgroundColor = backgroundColor;
|
_backgroundColor = backgroundColor;
|
||||||
[self.layer setNeedsDisplay];
|
[self.layer setNeedsDisplay];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (UIImage *)generateBorderImage:(out CGRect *)contentsCenter
|
- (UIImage *)borderImage:(out CGRect *)contentsCenter
|
||||||
{
|
{
|
||||||
static const CGFloat threshold = 0.001;
|
const CGFloat maxRadius = ({
|
||||||
|
const CGRect bounds = self.bounds;
|
||||||
const CGFloat maxRadius = MIN(self.bounds.size.height, self.bounds.size.width);
|
MIN(bounds.size.height, bounds.size.width);
|
||||||
|
});
|
||||||
const CGFloat radius = MAX(0, _borderRadius);
|
const CGFloat radius = MAX(0, _borderRadius);
|
||||||
const CGFloat topLeftRadius = MIN(_borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius, maxRadius);
|
const CGFloat topLeftRadius = MIN(_borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius, maxRadius);
|
||||||
const CGFloat topRightRadius = MIN(_borderTopRightRadius >= 0 ? _borderTopRightRadius : radius, maxRadius);
|
const CGFloat topRightRadius = MIN(_borderTopRightRadius >= 0 ? _borderTopRightRadius : radius, maxRadius);
|
||||||
const CGFloat bottomLeftRadius = MIN(_borderBottomLeftRadius >= 0 ? _borderBottomLeftRadius : radius, maxRadius);
|
const CGFloat bottomLeftRadius = MIN(_borderBottomLeftRadius >= 0 ? _borderBottomLeftRadius : radius, maxRadius);
|
||||||
const CGFloat bottomRightRadius = MIN(_borderBottomRightRadius >= 0 ? _borderBottomRightRadius : radius, maxRadius);
|
const CGFloat bottomRightRadius = MIN(_borderBottomRightRadius >= 0 ? _borderBottomRightRadius : radius, maxRadius);
|
||||||
|
|
||||||
const CGFloat borderWidth = MAX(0, _borderWidth);
|
const CGFloat borderWidth = MAX(0, _borderWidth);
|
||||||
|
@ -457,14 +461,10 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
||||||
const CGFloat bottomWidth = _borderBottomWidth >= 0 ? _borderBottomWidth : borderWidth;
|
const CGFloat bottomWidth = _borderBottomWidth >= 0 ? _borderBottomWidth : borderWidth;
|
||||||
const CGFloat leftWidth = _borderLeftWidth >= 0 ? _borderLeftWidth : borderWidth;
|
const CGFloat leftWidth = _borderLeftWidth >= 0 ? _borderLeftWidth : borderWidth;
|
||||||
|
|
||||||
if (topLeftRadius < threshold &&
|
if (topLeftRadius < RCTViewBorderThreshold && topRightRadius < RCTViewBorderThreshold &&
|
||||||
topRightRadius < threshold &&
|
bottomLeftRadius < RCTViewBorderThreshold && bottomRightRadius < RCTViewBorderThreshold &&
|
||||||
bottomLeftRadius < threshold &&
|
topWidth < RCTViewBorderThreshold && rightWidth < RCTViewBorderThreshold &&
|
||||||
bottomRightRadius < threshold &&
|
bottomWidth < RCTViewBorderThreshold && leftWidth < RCTViewBorderThreshold) {
|
||||||
topWidth < threshold &&
|
|
||||||
rightWidth < threshold &&
|
|
||||||
bottomWidth < threshold &&
|
|
||||||
leftWidth < threshold) {
|
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -486,14 +486,21 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
||||||
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
|
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
|
||||||
|
|
||||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||||
const CGRect rect = {CGPointZero, size};
|
const CGRect rect = {.size = size};
|
||||||
CGPathRef path = RCTPathCreateWithRoundedRect(rect, topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, bottomLeftRadius, bottomLeftRadius, bottomRightRadius, bottomRightRadius, NULL);
|
|
||||||
|
CGPathRef path;
|
||||||
|
const BOOL hasClipping = self.clipsToBounds;
|
||||||
|
if (hasClipping) {
|
||||||
|
path = CGPathCreateWithRect(rect, NULL);
|
||||||
|
} else {
|
||||||
|
path = RCTPathCreateWithRoundedRect(rect, topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, bottomLeftRadius, bottomLeftRadius, bottomRightRadius, bottomRightRadius, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
if (_backgroundColor) {
|
if (_backgroundColor) {
|
||||||
CGContextSaveGState(ctx);
|
CGContextSaveGState(ctx);
|
||||||
|
|
||||||
CGContextAddPath(ctx, path);
|
|
||||||
CGContextSetFillColorWithColor(ctx, _backgroundColor.CGColor);
|
CGContextSetFillColorWithColor(ctx, _backgroundColor.CGColor);
|
||||||
|
CGContextAddPath(ctx, path);
|
||||||
CGContextFillPath(ctx);
|
CGContextFillPath(ctx);
|
||||||
|
|
||||||
CGContextRestoreGState(ctx);
|
CGContextRestoreGState(ctx);
|
||||||
|
@ -502,29 +509,18 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
||||||
CGContextAddPath(ctx, path);
|
CGContextAddPath(ctx, path);
|
||||||
CGPathRelease(path);
|
CGPathRelease(path);
|
||||||
|
|
||||||
BOOL hasRadius = topLeftRadius > 0 || topRightRadius > 0 || bottomLeftRadius > 0 || bottomRightRadius > 0;
|
const BOOL hasRadius = topLeftRadius > 0 || topRightRadius > 0 || bottomLeftRadius > 0 || bottomRightRadius > 0;
|
||||||
if (hasRadius && topWidth > 0 && rightWidth > 0 && bottomWidth > 0 && leftWidth > 0) {
|
const UIEdgeInsets insetEdgeInsets = UIEdgeInsetsMake(topWidth, leftWidth, bottomWidth, rightWidth);
|
||||||
const UIEdgeInsets insetEdgeInsets = UIEdgeInsetsMake(topWidth, leftWidth, bottomWidth, rightWidth);
|
CGPathRef insetPath = RCTPathCreateWithRoundedRect(UIEdgeInsetsInsetRect(rect, insetEdgeInsets), innerTopLeftRadiusX, innerTopLeftRadiusY, innerTopRightRadiusX, innerTopRightRadiusY, innerBottomLeftRadiusX, innerBottomLeftRadiusY, innerBottomRightRadiusX, innerBottomRightRadiusY, NULL);
|
||||||
const CGRect insetRect = UIEdgeInsetsInsetRect(rect, insetEdgeInsets);
|
|
||||||
CGPathRef insetPath = RCTPathCreateWithRoundedRect(insetRect, innerTopLeftRadiusX, innerTopLeftRadiusY, innerTopRightRadiusX, innerTopRightRadiusY, innerBottomLeftRadiusX, innerBottomLeftRadiusY, innerBottomRightRadiusX, innerBottomRightRadiusY, NULL);
|
|
||||||
CGContextAddPath(ctx, insetPath);
|
|
||||||
CGPathRelease(insetPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
CGContextAddPath(ctx, insetPath);
|
||||||
CGContextEOClip(ctx);
|
CGContextEOClip(ctx);
|
||||||
|
|
||||||
BOOL hasEqualColor = !_borderTopColor && !_borderRightColor && !_borderBottomColor && !_borderLeftColor;
|
BOOL hasEqualColor = !_borderTopColor && !_borderRightColor && !_borderBottomColor && !_borderLeftColor;
|
||||||
BOOL hasEqualBorder = _borderWidth >= 0 && _borderTopWidth < 0 && _borderRightWidth < 0 && _borderBottomWidth < 0 && _borderLeftWidth < 0;
|
if ((hasClipping || !hasRadius) && hasEqualColor) {
|
||||||
if (!hasRadius && hasEqualBorder && hasEqualColor) {
|
|
||||||
CGContextSetStrokeColorWithColor(ctx, _borderColor);
|
|
||||||
CGContextSetLineWidth(ctx, 2 * _borderWidth);
|
|
||||||
CGContextClipToRect(ctx, rect);
|
|
||||||
CGContextStrokeRect(ctx, rect);
|
|
||||||
} else if (!hasRadius && hasEqualColor) {
|
|
||||||
CGContextSetFillColorWithColor(ctx, _borderColor);
|
CGContextSetFillColorWithColor(ctx, _borderColor);
|
||||||
CGContextAddRect(ctx, rect);
|
CGContextAddRect(ctx, rect);
|
||||||
const CGRect insetRect = UIEdgeInsetsInsetRect(rect, edgeInsets);
|
CGContextAddPath(ctx, insetPath);
|
||||||
CGContextAddRect(ctx, insetRect);
|
|
||||||
CGContextEOFillPath(ctx);
|
CGContextEOFillPath(ctx);
|
||||||
} else {
|
} else {
|
||||||
BOOL didSet = NO;
|
BOOL didSet = NO;
|
||||||
|
@ -660,6 +656,8 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CGPathRelease(insetPath);
|
||||||
|
|
||||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||||
UIGraphicsEndImageContext();
|
UIGraphicsEndImageContext();
|
||||||
|
|
||||||
|
@ -669,8 +667,8 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
||||||
|
|
||||||
- (void)displayLayer:(CALayer *)layer
|
- (void)displayLayer:(CALayer *)layer
|
||||||
{
|
{
|
||||||
CGRect contentsCenter = (CGRect){CGPointZero, {1, 1}};
|
CGRect contentsCenter = {.size = {1, 1}};
|
||||||
UIImage *image = [self generateBorderImage:&contentsCenter];
|
UIImage *image = [self borderImage:&contentsCenter];
|
||||||
|
|
||||||
if (image && RCTRunningInTestEnvironment()) {
|
if (image && RCTRunningInTestEnvironment()) {
|
||||||
const CGSize size = self.bounds.size;
|
const CGSize size = self.bounds.size;
|
||||||
|
@ -685,6 +683,46 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
||||||
layer.contentsCenter = contentsCenter;
|
layer.contentsCenter = contentsCenter;
|
||||||
layer.contentsScale = image.scale ?: 1.0;
|
layer.contentsScale = image.scale ?: 1.0;
|
||||||
layer.magnificationFilter = kCAFilterNearest;
|
layer.magnificationFilter = kCAFilterNearest;
|
||||||
|
layer.needsDisplayOnBoundsChange = image != nil;
|
||||||
|
|
||||||
|
[self updateClippingForLayer:layer];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateClippingForLayer:(CALayer *)layer
|
||||||
|
{
|
||||||
|
CALayer *mask = nil;
|
||||||
|
CGFloat cornerRadius = 0;
|
||||||
|
|
||||||
|
if (self.clipsToBounds) {
|
||||||
|
if (_borderRadius > 0 && _borderTopLeftRadius < 0 && _borderTopRightRadius < 0 && _borderBottomLeftRadius < 0 && _borderBottomRightRadius < 0) {
|
||||||
|
cornerRadius = _borderRadius;
|
||||||
|
} else {
|
||||||
|
const CGRect bounds = layer.bounds;
|
||||||
|
const CGFloat maxRadius = MIN(bounds.size.height, bounds.size.width);
|
||||||
|
const CGFloat radius = MAX(0, _borderRadius);
|
||||||
|
const CGFloat topLeftRadius = MIN(_borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius, maxRadius);
|
||||||
|
const CGFloat topRightRadius = MIN(_borderTopRightRadius >= 0 ? _borderTopRightRadius : radius, maxRadius);
|
||||||
|
const CGFloat bottomLeftRadius = MIN(_borderBottomLeftRadius >= 0 ? _borderBottomLeftRadius : radius, maxRadius);
|
||||||
|
const CGFloat bottomRightRadius = MIN(_borderBottomRightRadius >= 0 ? _borderBottomRightRadius : radius, maxRadius);
|
||||||
|
|
||||||
|
if (ABS(topLeftRadius - topRightRadius) < RCTViewBorderThreshold &&
|
||||||
|
ABS(topLeftRadius - bottomLeftRadius) < RCTViewBorderThreshold &&
|
||||||
|
ABS(topLeftRadius - bottomRightRadius) < RCTViewBorderThreshold) {
|
||||||
|
cornerRadius = topLeftRadius;
|
||||||
|
} else {
|
||||||
|
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
|
||||||
|
|
||||||
|
CGPathRef path = RCTPathCreateWithRoundedRect(bounds, topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, bottomLeftRadius, bottomLeftRadius, bottomRightRadius, bottomRightRadius, NULL);
|
||||||
|
shapeLayer.path = path;
|
||||||
|
CGPathRelease(path);
|
||||||
|
|
||||||
|
mask = shapeLayer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.cornerRadius = cornerRadius;
|
||||||
|
layer.mask = mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark Border Color
|
#pragma mark Border Color
|
||||||
|
|
Loading…
Reference in New Issue