[React Native] Fix view clipping when border radius is set

This commit is contained in:
Alex Akers 2015-05-26 02:12:28 -07:00
parent 9062bda79b
commit 81401064e5
2 changed files with 93 additions and 37 deletions

View File

@ -80,6 +80,13 @@ var styles = StyleSheet.create({
borderTopLeftRadius: 100,
},
border7: {
borderRadius: 20,
},
border7_inner: {
backgroundColor: 'blue',
flex: 1,
},
});
exports.title = 'Border';
@ -134,4 +141,15 @@ exports.examples = [
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>
);
}
},
];

View File

@ -15,6 +15,8 @@
#import "RCTUtils.h"
#import "UIView+React.h"
static const CGFloat RCTViewBorderThreshold = 0.001;
static UIView *RCTViewHitTest(UIView *view, CGPoint point, UIEvent *event)
{
for (UIView *subview in [view.subviews reverseObjectEnumerator]) {
@ -436,19 +438,21 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
if ([_backgroundColor isEqual:backgroundColor]) {
return;
}
_backgroundColor = backgroundColor;
[self.layer setNeedsDisplay];
}
- (UIImage *)generateBorderImage:(out CGRect *)contentsCenter
- (UIImage *)borderImage:(out CGRect *)contentsCenter
{
static const CGFloat threshold = 0.001;
const CGFloat maxRadius = MIN(self.bounds.size.height, self.bounds.size.width);
const CGFloat maxRadius = ({
const CGRect bounds = self.bounds;
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 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);
const CGFloat borderWidth = MAX(0, _borderWidth);
@ -457,14 +461,10 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
const CGFloat bottomWidth = _borderBottomWidth >= 0 ? _borderBottomWidth : borderWidth;
const CGFloat leftWidth = _borderLeftWidth >= 0 ? _borderLeftWidth : borderWidth;
if (topLeftRadius < threshold &&
topRightRadius < threshold &&
bottomLeftRadius < threshold &&
bottomRightRadius < threshold &&
topWidth < threshold &&
rightWidth < threshold &&
bottomWidth < threshold &&
leftWidth < threshold) {
if (topLeftRadius < RCTViewBorderThreshold && topRightRadius < RCTViewBorderThreshold &&
bottomLeftRadius < RCTViewBorderThreshold && bottomRightRadius < RCTViewBorderThreshold &&
topWidth < RCTViewBorderThreshold && rightWidth < RCTViewBorderThreshold &&
bottomWidth < RCTViewBorderThreshold && leftWidth < RCTViewBorderThreshold) {
return nil;
}
@ -486,14 +486,21 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
const CGRect rect = {CGPointZero, size};
CGPathRef path = RCTPathCreateWithRoundedRect(rect, topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, bottomLeftRadius, bottomLeftRadius, bottomRightRadius, bottomRightRadius, NULL);
const CGRect rect = {.size = size};
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) {
CGContextSaveGState(ctx);
CGContextAddPath(ctx, path);
CGContextSetFillColorWithColor(ctx, _backgroundColor.CGColor);
CGContextAddPath(ctx, path);
CGContextFillPath(ctx);
CGContextRestoreGState(ctx);
@ -502,29 +509,18 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
CGContextAddPath(ctx, path);
CGPathRelease(path);
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 CGRect insetRect = UIEdgeInsetsInsetRect(rect, insetEdgeInsets);
CGPathRef insetPath = RCTPathCreateWithRoundedRect(insetRect, innerTopLeftRadiusX, innerTopLeftRadiusY, innerTopRightRadiusX, innerTopRightRadiusY, innerBottomLeftRadiusX, innerBottomLeftRadiusY, innerBottomRightRadiusX, innerBottomRightRadiusY, NULL);
CGContextAddPath(ctx, insetPath);
CGPathRelease(insetPath);
}
const BOOL hasRadius = topLeftRadius > 0 || topRightRadius > 0 || bottomLeftRadius > 0 || bottomRightRadius > 0;
const UIEdgeInsets insetEdgeInsets = UIEdgeInsetsMake(topWidth, leftWidth, bottomWidth, rightWidth);
CGPathRef insetPath = RCTPathCreateWithRoundedRect(UIEdgeInsetsInsetRect(rect, insetEdgeInsets), innerTopLeftRadiusX, innerTopLeftRadiusY, innerTopRightRadiusX, innerTopRightRadiusY, innerBottomLeftRadiusX, innerBottomLeftRadiusY, innerBottomRightRadiusX, innerBottomRightRadiusY, NULL);
CGContextAddPath(ctx, insetPath);
CGContextEOClip(ctx);
BOOL hasEqualColor = !_borderTopColor && !_borderRightColor && !_borderBottomColor && !_borderLeftColor;
BOOL hasEqualBorder = _borderWidth >= 0 && _borderTopWidth < 0 && _borderRightWidth < 0 && _borderBottomWidth < 0 && _borderLeftWidth < 0;
if (!hasRadius && hasEqualBorder && hasEqualColor) {
CGContextSetStrokeColorWithColor(ctx, _borderColor);
CGContextSetLineWidth(ctx, 2 * _borderWidth);
CGContextClipToRect(ctx, rect);
CGContextStrokeRect(ctx, rect);
} else if (!hasRadius && hasEqualColor) {
if ((hasClipping || !hasRadius) && hasEqualColor) {
CGContextSetFillColorWithColor(ctx, _borderColor);
CGContextAddRect(ctx, rect);
const CGRect insetRect = UIEdgeInsetsInsetRect(rect, edgeInsets);
CGContextAddRect(ctx, insetRect);
CGContextAddPath(ctx, insetPath);
CGContextEOFillPath(ctx);
} else {
BOOL didSet = NO;
@ -660,6 +656,8 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
}
}
CGPathRelease(insetPath);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
@ -669,8 +667,8 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
- (void)displayLayer:(CALayer *)layer
{
CGRect contentsCenter = (CGRect){CGPointZero, {1, 1}};
UIImage *image = [self generateBorderImage:&contentsCenter];
CGRect contentsCenter = {.size = {1, 1}};
UIImage *image = [self borderImage:&contentsCenter];
if (image && RCTRunningInTestEnvironment()) {
const CGSize size = self.bounds.size;
@ -685,6 +683,46 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
layer.contentsCenter = contentsCenter;
layer.contentsScale = image.scale ?: 1.0;
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