From 81401064e5bc0e50de5d77ec9e2384a70e43e2f4 Mon Sep 17 00:00:00 2001 From: Alex Akers Date: Tue, 26 May 2015 02:12:28 -0700 Subject: [PATCH] [React Native] Fix view clipping when border radius is set --- Examples/UIExplorer/BorderExample.js | 18 +++++ React/Views/RCTView.m | 112 ++++++++++++++++++--------- 2 files changed, 93 insertions(+), 37 deletions(-) diff --git a/Examples/UIExplorer/BorderExample.js b/Examples/UIExplorer/BorderExample.js index f49329fae..7789459f8 100644 --- a/Examples/UIExplorer/BorderExample.js +++ b/Examples/UIExplorer/BorderExample.js @@ -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 ; } }, + { + title: 'Custom Borders', + description: 'borderRadius & clipping', + render() { + return ( + + + + ); + } + }, ]; diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m index 61f8240e8..08f713d9d 100644 --- a/React/Views/RCTView.m +++ b/React/Views/RCTView.m @@ -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