Skip to content

Commit

Permalink
fix(ios): view shadow missing with multiple borderRadius values
Browse files Browse the repository at this point in the history
use cornerradius property of view’s layer if one border radius is set on all corners

Fixes TIMOB-28103, TIMOB-28110
  • Loading branch information
vijaysingh-axway authored and sgtcoolguy committed Sep 9, 2020
1 parent fa8f547 commit 2a38bf3
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 36 deletions.
5 changes: 4 additions & 1 deletion iphone/TitaniumKit/TitaniumKit/Sources/API/TiUIView.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ void ModifyScrollViewForKeyboardHeightAndContentHeightWithResponderRect(UIScroll
TiProxy *proxy;
TiAnimation *animation;

CAShapeLayer *_shadowLayer;
CALayer *gradientLayer;
CALayer *bgdImageLayer;
CAShapeLayer *_borderLayer;
int clipMode;

CGAffineTransform virtualParentTransform;
Expand Down Expand Up @@ -146,6 +148,7 @@ void ModifyScrollViewForKeyboardHeightAndContentHeightWithResponderRect(UIScroll
- (BOOL)proxyHasGestureListeners;
- (void)ensureGestureListeners;
- (void)updateClipping;
- (UIBezierPath *)bezierPathOfView;
/**
Returns CA layer for the background image of the view.
*/
Expand All @@ -157,7 +160,7 @@ void ModifyScrollViewForKeyboardHeightAndContentHeightWithResponderRect(UIScroll
/**
Returns CA layer for shadow component of the view.
*/
- (CALayer *)shadowLayer;
- (CAShapeLayer *)shadowLayer;
/**
Tells the view to start specified animation.
@param newAnimation The animation to start.
Expand Down
215 changes: 180 additions & 35 deletions iphone/TitaniumKit/TitaniumKit/Sources/API/TiUIView.m
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,18 @@ - (void)checkBounds
}
[self frameSizeChanged:[TiUtils viewPositionRect:self] bounds:newBounds];
}

if (_shadowLayer && _shadowLayer.shadowOpacity > 0.0f && self.layer.superlayer && _shadowLayer != self.layer) {
// Set _shadowLayer frame and insert at right place
[self.layer.superlayer insertSublayer:_shadowLayer below:self.layer];
_shadowLayer.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.bounds.size.width, self.bounds.size.height);
}

if (_borderLayer && _borderLayer != self.layer) {
// Set _borderLayer frame and path
_borderLayer.path = [self bezierPathOfView].CGPath;
_borderLayer.frame = self.bounds;
}
#endif
}

Expand Down Expand Up @@ -474,6 +486,27 @@ - (void)fillFrameToRect:(TiRect *)rect
[rect setRect:r];
}

- (CAShapeLayer *)borderLayer
{
// If borderRadius has multiple values, create a custom borderlayer and add as sublayer on self.layer. Otherwise use self.layer.
NSArray *array = [self cornerArrayFromRadius:[proxy valueForUndefinedKey:@"borderRadius"]];
if ((!array || array.count <= 1) && _borderLayer != self.layer) {
self.layer.mask = nil;
self.layer.borderColor = _borderLayer.strokeColor;
self.layer.borderWidth = _borderLayer.lineWidth / 2;
[_borderLayer removeFromSuperlayer];
RELEASE_TO_NIL(_borderLayer);
_borderLayer = (CAShapeLayer *)self.layer;
} else if ((array.count > 1) && (!_borderLayer || _borderLayer == self.layer)) {
_borderLayer = [[CAShapeLayer alloc] init];
_borderLayer.fillColor = UIColor.clearColor.CGColor;
_borderLayer.strokeColor = self.layer.borderColor;
_borderLayer.lineWidth = self.layer.borderWidth * 2;
[self.layer addSublayer:_borderLayer];
}
return _borderLayer;
}

#pragma mark Public APIs

- (void)setTintColor_:(id)color
Expand All @@ -485,17 +518,29 @@ - (void)setTintColor_:(id)color
- (void)setBorderColor_:(id)color
{
TiColor *ticolor = [TiUtils colorValue:color];
self.layer.borderWidth = MAX(self.layer.borderWidth, 1);
self.layer.borderColor = [ticolor _color].CGColor;
CAShapeLayer *layer = [self borderLayer];
if (layer == self.layer) {
layer.borderWidth = MAX(layer.borderWidth, 1);
layer.borderColor = [ticolor _color].CGColor;
} else {
layer.lineWidth = MAX(layer.lineWidth * 2, 1 * 2);
layer.strokeColor = [ticolor _color].CGColor;
}
}

- (void)setBorderWidth_:(id)w
{
TiDimension theDim = TiDimensionFromObject(w);
CGFloat borderWidth = 0;
if (TiDimensionIsDip(theDim)) {
self.layer.borderWidth = MAX(theDim.value, 0);
borderWidth = MAX(theDim.value, 0);
}

CAShapeLayer *layer = [self borderLayer];
if (layer == self.layer) {
layer.borderWidth = MAX(borderWidth, 0);
} else {
self.layer.borderWidth = 0;
layer.lineWidth = MAX(borderWidth * 2, 0);
}
[self updateClipping];
}
Expand Down Expand Up @@ -597,7 +642,12 @@ - (void)setBackgroundImage_:(id)image
bgdImageLayer = [[CALayer alloc] init];
[bgdImageLayer setFrame:[self bounds]];
bgdImageLayer.masksToBounds = YES;
bgdImageLayer.cornerRadius = self.layer.cornerRadius;
NSArray *cornerRadiusArray = [self cornerArrayFromRadius:[proxy valueForUndefinedKey:@"borderRadius"]];
if (cornerRadiusArray && cornerRadiusArray.count > 0) {
[self addCornerRadius:cornerRadiusArray toLayer:bgdImageLayer];
} else {
bgdImageLayer.cornerRadius = self.layer.cornerRadius;
}
if (gradientLayer != nil) {
[[self gradientWrapperView].layer insertSublayer:bgdImageLayer above:gradientLayer];
} else {
Expand Down Expand Up @@ -653,7 +703,7 @@ - (CGFloat)radiusFromObject:(id)object
return TiDimensionIsDip(theDim) ? MAX(theDim.value, 0) : 0;
}

- (void)addCornerRadius:(NSArray *)radiusArray toLayer:(CALayer *)viewLayer
- (UIBezierPath *)bezierPathOfSize:(CGSize)size forRadiusArray:(NSArray *)radiusArray
{
CGFloat topLeftRadius;
CGFloat bottomLeftRadius;
Expand Down Expand Up @@ -681,36 +731,51 @@ - (void)addCornerRadius:(NSArray *)radiusArray toLayer:(CALayer *)viewLayer
bottomLeftRadius = radius;
}

CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] initWithLayer:viewLayer];
UIBezierPath *borderPath = [UIBezierPath bezierPath];
[borderPath moveToPoint:CGPointMake(0 + topLeftRadius, 0)];
[borderPath addLineToPoint:CGPointMake(size.width - topRightRadius, 0)];
[borderPath addArcWithCenter:CGPointMake(size.width - topRightRadius, topRightRadius) radius:topRightRadius startAngle:3 * M_PI_2 endAngle:0 clockwise:YES];
[borderPath addLineToPoint:CGPointMake(size.width, size.height - bottomRightRadius)];
[borderPath addArcWithCenter:CGPointMake(size.width - bottomRightRadius, size.height - bottomRightRadius) radius:bottomRightRadius startAngle:0 endAngle:M_PI_2 clockwise:YES];
[borderPath addLineToPoint:CGPointMake(bottomLeftRadius, size.height)];
[borderPath addArcWithCenter:CGPointMake(bottomLeftRadius, size.height - bottomLeftRadius) radius:bottomLeftRadius startAngle:M_PI_2 endAngle:M_PI clockwise:YES];
[borderPath addLineToPoint:CGPointMake(0, topLeftRadius)];
[borderPath addArcWithCenter:CGPointMake(topLeftRadius, topLeftRadius) radius:topLeftRadius startAngle:M_PI endAngle:3 * M_PI_2 clockwise:YES];
[borderPath closePath];
return borderPath;
}

- (void)addCornerRadius:(NSArray *)radiusArray toLayer:(CALayer *)viewLayer
{
CAShapeLayer *shapeLayer = [[[CAShapeLayer alloc] initWithLayer:viewLayer] autorelease];
CGSize size = viewLayer.bounds.size;
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:CGPointMake(0 + topLeftRadius, 0)];
[bezierPath addLineToPoint:CGPointMake(size.width - topRightRadius, 0)];
[bezierPath addArcWithCenter:CGPointMake(size.width - topRightRadius, topRightRadius) radius:topRightRadius startAngle:3 * M_PI_2 endAngle:0 clockwise:YES];
[bezierPath addLineToPoint:CGPointMake(size.width, size.height - bottomRightRadius)];
[bezierPath addArcWithCenter:CGPointMake(size.width - bottomRightRadius, size.height - bottomRightRadius) radius:bottomRightRadius startAngle:0 endAngle:M_PI_2 clockwise:YES];
[bezierPath addLineToPoint:CGPointMake(bottomLeftRadius, size.height)];
[bezierPath addArcWithCenter:CGPointMake(bottomLeftRadius, size.height - bottomLeftRadius) radius:bottomLeftRadius startAngle:M_PI_2 endAngle:M_PI clockwise:YES];
[bezierPath addLineToPoint:CGPointMake(0, topLeftRadius)];
[bezierPath addArcWithCenter:CGPointMake(topLeftRadius, topLeftRadius) radius:topLeftRadius startAngle:M_PI endAngle:3 * M_PI_2 clockwise:YES];
[bezierPath closePath];
UIBezierPath *bezierPath = [self bezierPathOfSize:size forRadiusArray:radiusArray];

shapeLayer.path = bezierPath.CGPath;
shapeLayer.frame = viewLayer.bounds;
viewLayer.mask = shapeLayer;
[self updateViewShadowPath];
}

- (void)updateBorderRadius:(id)radius
- (NSArray *)cornerArrayFromRadius:(id)radius
{
NSArray *cornerRadiusArray;
NSArray *cornerRadiusArray = nil;
if ([radius isKindOfClass:[NSString class]]) {
cornerRadiusArray = [(NSString *)radius componentsSeparatedByString:@" "];
} else if ([radius isKindOfClass:[NSArray class]]) {
cornerRadiusArray = radius;
} else if ([radius isKindOfClass:[NSNumber class]]) {
cornerRadiusArray = [NSArray arrayWithObject:radius];
} else {
NSLog(@"[WARN] Invalid value specified for borderRadius.");
}
return cornerRadiusArray;
}

- (void)updateBorderRadius:(id)radius
{
NSArray *cornerRadiusArray = [self cornerArrayFromRadius:radius];

if (!cornerRadiusArray) {
NSLog(@"[WARN] No value specified for borderRadius.");
return;
}

Expand All @@ -719,18 +784,46 @@ - (void)updateBorderRadius:(id)radius
return;
}

[self addCornerRadius:cornerRadiusArray toLayer:self.layer];
if (bgdImageLayer != nil) {
[self addCornerRadius:cornerRadiusArray toLayer:bgdImageLayer];
}
if (gradientLayer != nil) {
[self addCornerRadius:cornerRadiusArray toLayer:gradientLayer];
// If there is 1 corner radius to be set for all corner, use cornerRadius of self.layer. Otherwise use bezierpath.
if (cornerRadiusArray.count == 1) {
CGFloat cornerRadius = [self radiusFromObject:cornerRadiusArray[0]];
self.layer.mask = nil;
self.layer.cornerRadius = cornerRadius;
if (bgdImageLayer != nil) {
bgdImageLayer.mask = nil;
bgdImageLayer.cornerRadius = cornerRadius;
}
if (gradientLayer != nil) {
gradientLayer.mask = nil;
gradientLayer.cornerRadius = cornerRadius;
}
} else {
self.layer.cornerRadius = 0;
[self addCornerRadius:cornerRadiusArray toLayer:self.layer];
if (bgdImageLayer != nil) {
bgdImageLayer.cornerRadius = 0;
[self addCornerRadius:cornerRadiusArray toLayer:bgdImageLayer];
}
if (gradientLayer != nil) {
gradientLayer.cornerRadius = 0;
[self addCornerRadius:cornerRadiusArray toLayer:gradientLayer];
}
}
[self updateClipping];
[self updateViewShadowPath];
}

- (void)setBorderRadius_:(id)radius
{
// Ensure that proper shadowLayer and borderLayer is there.
// e.g if raidus has multiple values, custom shadowLayer and borderLayer need to added. if it has single value self.layer should be used.

if (_shadowLayer) {
[self shadowLayer];
}
if (_borderLayer) {
[self borderLayer];
}
[self updateBorderRadius:radius];
}

Expand Down Expand Up @@ -789,7 +882,12 @@ - (void)setBackgroundGradient_:(id)arg
[gradientLayer setNeedsDisplayOnBoundsChange:YES];
[gradientLayer setFrame:[self bounds]];
[gradientLayer setNeedsDisplay];
gradientLayer.cornerRadius = self.layer.cornerRadius;
NSArray *cornerRadiusArray = [self cornerArrayFromRadius:[proxy valueForUndefinedKey:@"borderRadius"]];
if (cornerRadiusArray && cornerRadiusArray.count > 0) {
[self addCornerRadius:cornerRadiusArray toLayer:gradientLayer];
} else {
gradientLayer.cornerRadius = self.layer.cornerRadius;
}
gradientLayer.masksToBounds = YES;
[[self gradientWrapperView].layer insertSublayer:gradientLayer atIndex:0];
} else {
Expand All @@ -804,10 +902,10 @@ - (void)updateClipping
//Explicitly overridden
self.clipsToBounds = (clipMode > 0);
} else {
if ([self shadowLayer].shadowOpacity > 0) {
if (_shadowLayer.shadowOpacity > 0) {
//If shadow is visible, disble clipping
self.clipsToBounds = NO;
} else if (self.layer.borderWidth > 0 || self.layer.cornerRadius > 0) {
} else if (self.layer.borderWidth > 0 || self.layer.cornerRadius > 0 || [proxy valueForUndefinedKey:@"borderRadius"]) {
//If borderWidth > 0, or borderRadius > 0 enable clipping
self.clipsToBounds = YES;
} else if ([[self proxy] isKindOfClass:[TiViewProxy class]]) {
Expand All @@ -830,9 +928,46 @@ - (void)setClipMode_:(id)arg
This section of code for shadow support adapted from contributions by Martin Guillon
See https://github.com/appcelerator/titanium_mobile/pull/2996
*/
- (CALayer *)shadowLayer
- (void)assignShadowPropertyFromLayer:(CALayer *)fromLayer toLayer:(CALayer *)toLayer
{
toLayer.shadowColor = fromLayer.shadowColor;
toLayer.shadowOffset = fromLayer.shadowOffset;
toLayer.shadowRadius = fromLayer.shadowRadius;
toLayer.shadowOpacity = fromLayer.shadowOpacity;
}

- (CAShapeLayer *)shadowLayer
{
// If there is single cborderRadius, use self.layer as shadwoLayer.
// Shadow animation does not work if shadowLayer is added on self.layer.superlayer
// But in this case, shadwoLayer is self.layer. So animation will work on shadow as well.
NSArray *array = [self cornerArrayFromRadius:[proxy valueForUndefinedKey:@"borderRadius"]];
if ((!array || array.count <= 1) && _shadowLayer != self.layer) {
self.layer.mask = nil;
[self assignShadowPropertyFromLayer:_shadowLayer toLayer:self.layer];
[_shadowLayer removeFromSuperlayer];
RELEASE_TO_NIL(_shadowLayer);
_shadowLayer = self.layer;
} else if ((array.count > 1) && (!_shadowLayer || _shadowLayer == self.layer)) {
_shadowLayer = [[CAShapeLayer alloc] init];
_shadowLayer.fillColor = UIColor.clearColor.CGColor;
[self assignShadowPropertyFromLayer:self.layer toLayer:_shadowLayer];
if (self.layer.superlayer) {
[self.layer.superlayer insertSublayer:_shadowLayer below:self.layer];
}
}
return _shadowLayer;
}

- (UIBezierPath *)bezierPathOfView
{
return [self layer];
if ([proxy valueForUndefinedKey:@"borderRadius"]) {
NSArray *array = [self cornerArrayFromRadius:[proxy valueForUndefinedKey:@"borderRadius"]];
if (array.count > 1) {
return [self bezierPathOfSize:self.bounds.size forRadiusArray:array];
}
}
return [UIBezierPath bezierPathWithRoundedRect:[self bounds] cornerRadius:self.layer.cornerRadius];
}

- (void)setViewShadowOffset_:(id)arg
Expand Down Expand Up @@ -893,9 +1028,19 @@ - (void)setVerticalMotionEffect_:(id)motionEffect

- (void)updateViewShadowPath
{
if ([self shadowLayer].shadowOpacity > 0.0f) {
if (_shadowLayer.shadowOpacity > 0.0f) {
//to speedup things
[self shadowLayer].shadowPath = [UIBezierPath bezierPathWithRoundedRect:[self bounds] cornerRadius:self.layer.cornerRadius].CGPath;
UIBezierPath *bezierPath = [self bezierPathOfView];
if (_shadowLayer != self.layer) {
_shadowLayer.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.bounds.size.width, self.bounds.size.height);
} else {
_shadowLayer.bounds = self.bounds;
}
_shadowLayer.shadowPath = bezierPath.CGPath;
}
if (_borderLayer && _borderLayer != self.layer) {
_borderLayer.path = [self bezierPathOfView].CGPath;
_borderLayer.bounds = self.bounds;
}
}

Expand Down
13 changes: 13 additions & 0 deletions iphone/TitaniumKit/TitaniumKit/Sources/Modules/TiUIViewProxy.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@
#import "TiUIViewProxy.h"

@implementation TiUIViewProxy

static NSArray *uiviewKeySequence;

#pragma mark Internal

- (NSArray *)keySequence
{
if (uiviewKeySequence == nil) {
uiviewKeySequence = [[NSArray arrayWithObjects:@"borderRadius", @"borderWidth", @"borderColor", @"viewShadowColor", @"viewShadowRadius", @"viewShadowOffset", nil] retain];
}
return uiviewKeySequence;
}

- (NSString *)apiName
{
return @"Ti.UI.View";
Expand Down

0 comments on commit 2a38bf3

Please sign in to comment.