[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, 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>
);
}
},
]; ];

View File

@ -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