[React Native] [FRC - Don't accept] View border support

This commit is contained in:
Nick Lockwood 2015-03-26 01:43:17 -07:00
parent d2206d492d
commit 330c1abbff
9 changed files with 342 additions and 181 deletions

View File

@ -0,0 +1,90 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*/
'use strict';
var React = require('react-native');
var {
StyleSheet,
View
} = React;
var styles = StyleSheet.create({
box: {
width: 100,
height: 100,
},
border1: {
borderWidth: 10,
borderColor: 'brown',
},
borderRadius: {
borderWidth: 10,
borderRadius: 10,
borderColor: 'cyan',
},
border2: {
borderWidth: 10,
borderTopColor: 'red',
borderRightColor: 'yellow',
borderBottomColor: 'green',
borderLeftColor: 'blue',
},
border3: {
borderColor: 'purple',
borderTopWidth: 10,
borderRightWidth: 20,
borderBottomWidth: 30,
borderLeftWidth: 40,
},
border4: {
borderTopWidth: 10,
borderTopColor: 'red',
borderRightWidth: 20,
borderRightColor: 'yellow',
borderBottomWidth: 30,
borderBottomColor: 'green',
borderLeftWidth: 40,
borderLeftColor: 'blue',
},
});
exports.title = 'Border';
exports.description = 'View borders';
exports.examples = [
{
title: 'Equal-Width / Same-Color',
description: 'borderWidth & borderColor',
render() {
return <View style={[styles.box, styles.border1]} />;
}
},
{
title: 'Equal-Width / Same-Color',
description: 'borderWidth & borderColor',
render() {
return <View style={[styles.box, styles.borderRadius]} />;
}
},
{
title: 'Equal-Width Borders',
description: 'borderWidth & border*Color',
render() {
return <View style={[styles.box, styles.border2]} />;
}
},
{
title: 'Same-Color Borders',
description: 'border*Width & borderColor',
render() {
return <View style={[styles.box, styles.border3]} />;
}
},
{
title: 'Custom Borders',
description: 'border*Width & border*Color',
render() {
return <View style={[styles.box, styles.border4]} />;
}
},
];

View File

@ -55,6 +55,7 @@ var APIS = [
require('./AlertIOSExample'), require('./AlertIOSExample'),
require('./AppStateIOSExample'), require('./AppStateIOSExample'),
require('./AsyncStorageExample'), require('./AsyncStorageExample'),
require('./BorderExample'),
require('./CameraRollExample.ios'), require('./CameraRollExample.ios'),
require('./GeolocationExample'), require('./GeolocationExample'),
require('./LayoutExample'), require('./LayoutExample'),

View File

@ -437,9 +437,6 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
completion(YES); completion(YES);
} }
// TODO: deprecate this
[view reactSetBorders];
// Animate view creation // Animate view creation
BOOL shouldAnimateCreation = isNew && ![parentsAreNew[ii] boolValue]; BOOL shouldAnimateCreation = isNew && ![parentsAreNew[ii] boolValue];
RCTAnimation *createAnimation = _layoutAnimation.createAnimation; RCTAnimation *createAnimation = _layoutAnimation.createAnimation;

View File

@ -21,7 +21,7 @@
RCT_EXPORT_VIEW_PROPERTY(selected, BOOL); RCT_EXPORT_VIEW_PROPERTY(selected, BOOL);
RCT_EXPORT_VIEW_PROPERTY(icon, NSString); RCT_EXPORT_VIEW_PROPERTY(icon, NSString);
RCT_REMAP_VIEW_PROPERTY(selectedIcon, barItem.selectedImage, NSString); RCT_REMAP_VIEW_PROPERTY(selectedIcon, barItem.selectedImage, UIImage);
RCT_REMAP_VIEW_PROPERTY(badgeValue, barItem.badgeValue, NSString); RCT_REMAP_VIEW_PROPERTY(badgeValue, barItem.badgeValue, NSString);
RCT_CUSTOM_VIEW_PROPERTY(title, NSString, RCTTabBarItem) RCT_CUSTOM_VIEW_PROPERTY(title, NSString, RCTTabBarItem)
{ {

View File

@ -13,6 +13,13 @@
#import "RCTPointerEvents.h" #import "RCTPointerEvents.h"
typedef NS_ENUM(NSInteger, RCTBorderSide) {
RCTBorderSideTop,
RCTBorderSideRight,
RCTBorderSideBottom,
RCTBorderSideLeft
};
@protocol RCTAutoInsetsProtocol; @protocol RCTAutoInsetsProtocol;
@interface RCTView : UIView @interface RCTView : UIView
@ -48,4 +55,22 @@
*/ */
- (void)updateClippedSubviews; - (void)updateClippedSubviews;
/**
* Border colors.
*/
@property (nonatomic, assign) CGColorRef borderTopColor;
@property (nonatomic, assign) CGColorRef borderRightColor;
@property (nonatomic, assign) CGColorRef borderBottomColor;
@property (nonatomic, assign) CGColorRef borderLeftColor;
@property (nonatomic, assign) CGColorRef borderColor;
/**
* Border widths.
*/
@property (nonatomic, assign) CGFloat borderTopWidth;
@property (nonatomic, assign) CGFloat borderRightWidth;
@property (nonatomic, assign) CGFloat borderBottomWidth;
@property (nonatomic, assign) CGFloat borderLeftWidth;
@property (nonatomic, assign) CGFloat borderWidth;
@end @end

View File

@ -14,6 +14,8 @@
#import "RCTLog.h" #import "RCTLog.h"
#import "UIView+ReactKit.h" #import "UIView+ReactKit.h"
static const RCTBorderSide RCTBorderSideCount = 4;
@implementation UIView (RCTViewUnmounting) @implementation UIView (RCTViewUnmounting)
- (void)react_remountAllSubviews - (void)react_remountAllSubviews
@ -91,6 +93,8 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
@implementation RCTView @implementation RCTView
{ {
NSMutableArray *_reactSubviews; NSMutableArray *_reactSubviews;
CAShapeLayer *_borderLayers[RCTBorderSideCount];
CGFloat _borderWidths[RCTBorderSideCount];
} }
- (NSString *)accessibilityLabel - (NSString *)accessibilityLabel
@ -106,7 +110,7 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
_pointerEvents = pointerEvents; _pointerEvents = pointerEvents;
self.userInteractionEnabled = (pointerEvents != RCTPointerEventsNone); self.userInteractionEnabled = (pointerEvents != RCTPointerEventsNone);
if (pointerEvents == RCTPointerEventsBoxNone) { if (pointerEvents == RCTPointerEventsBoxNone) {
self.accessibilityViewIsModal = NO; // TODO: find out what this is for self.accessibilityViewIsModal = NO;
} }
} }
@ -368,9 +372,193 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
// to updateClippedSubviews manually after loading // to updateClippedSubviews manually after loading
[super layoutSubviews]; [super layoutSubviews];
if (_reactSubviews) { if (_reactSubviews) {
[self updateClippedSubviews]; [self updateClippedSubviews];
} }
for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
if (_borderLayers[side]) [self updatePathForShapeLayerForSide:side];
}
}
- (void)layoutSublayersOfLayer:(CALayer *)layer
{
[super layoutSublayersOfLayer:layer];
const CGRect bounds = layer.bounds;
for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
_borderLayers[side].frame = bounds;
}
}
- (BOOL)getTrapezoidPoints:(CGPoint[4])outPoints forSide:(RCTBorderSide)side
{
const CGRect bounds = self.layer.bounds;
const CGFloat minX = CGRectGetMinX(bounds);
const CGFloat maxX = CGRectGetMaxX(bounds);
const CGFloat minY = CGRectGetMinY(bounds);
const CGFloat maxY = CGRectGetMaxY(bounds);
#define BW(SIDE) [self borderWidthForSide:RCTBorderSide##SIDE]
switch (side) {
case RCTBorderSideRight:
outPoints[0] = CGPointMake(maxX - BW(Right), maxY - BW(Bottom));
outPoints[1] = CGPointMake(maxX - BW(Right), minY + BW(Top));
outPoints[2] = CGPointMake(maxX, minY);
outPoints[3] = CGPointMake(maxX, maxY);
break;
case RCTBorderSideBottom:
outPoints[0] = CGPointMake(minX + BW(Left), maxY - BW(Bottom));
outPoints[1] = CGPointMake(maxX - BW(Right), maxY - BW(Bottom));
outPoints[2] = CGPointMake(maxX, maxY);
outPoints[3] = CGPointMake(minX, maxY);
break;
case RCTBorderSideLeft:
outPoints[0] = CGPointMake(minX + BW(Left), minY + BW(Top));
outPoints[1] = CGPointMake(minX + BW(Left), maxY - BW(Bottom));
outPoints[2] = CGPointMake(minX, maxY);
outPoints[3] = CGPointMake(minX, minY);
break;
case RCTBorderSideTop:
outPoints[0] = CGPointMake(maxX - BW(Right), minY + BW(Top));
outPoints[1] = CGPointMake(minX + BW(Left), minY + BW(Top));
outPoints[2] = CGPointMake(minX, minY);
outPoints[3] = CGPointMake(maxX, minY);
break;
}
return YES;
}
- (CAShapeLayer *)createShapeLayerIfNotExistsForSide:(RCTBorderSide)side
{
CAShapeLayer *borderLayer = _borderLayers[side];
if (!borderLayer) {
borderLayer = [CAShapeLayer layer];
borderLayer.fillColor = self.layer.borderColor;
[self.layer addSublayer:borderLayer];
_borderLayers[side] = borderLayer;
}
return borderLayer;
}
- (void)updatePathForShapeLayerForSide:(RCTBorderSide)side
{
CAShapeLayer *borderLayer = [self createShapeLayerIfNotExistsForSide:side];
CGPoint trapezoidPoints[4];
[self getTrapezoidPoints:trapezoidPoints forSide:side];
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddLines(path, NULL, trapezoidPoints, 4);
CGPathCloseSubpath(path);
borderLayer.path = path;
CGPathRelease(path);
}
- (void)updateBorderLayers
{
BOOL widthsAndColorsSame = YES;
CGFloat width = _borderWidths[0];
CGColorRef color = _borderLayers[0].fillColor;
for (RCTBorderSide side = 1; side < RCTBorderSideCount; side++) {
CAShapeLayer *layer = _borderLayers[side];
if (_borderWidths[side] != width || (layer && !CGColorEqualToColor(layer.fillColor, color))) {
widthsAndColorsSame = NO;
break;
}
}
if (widthsAndColorsSame) {
// Set main layer border
if (width) {
_borderWidth = self.layer.borderWidth = width;
}
if (color) {
self.layer.borderColor = color;
}
// Remove border layers
for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
[_borderLayers[side] removeFromSuperlayer];
_borderLayers[side] = nil;
}
} else {
// Clear main layer border
self.layer.borderWidth = 0;
// Set up border layers
for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
[self updatePathForShapeLayerForSide:side];
}
}
}
- (CGFloat)borderWidthForSide:(RCTBorderSide)side
{
return _borderWidths[side] ?: _borderWidth;
}
- (void)setBorderWidth:(CGFloat)width forSide:(RCTBorderSide)side
{
_borderWidths[side] = width;
[self updateBorderLayers];
}
#define BORDER_WIDTH(SIDE) \
- (CGFloat)border##SIDE##Width { return [self borderWidthForSide:RCTBorderSide##SIDE]; } \
- (void)setBorder##SIDE##Width:(CGFloat)width { [self setBorderWidth:width forSide:RCTBorderSide##SIDE]; }
BORDER_WIDTH(Top)
BORDER_WIDTH(Right)
BORDER_WIDTH(Bottom)
BORDER_WIDTH(Left)
- (CGColorRef)borderColorForSide:(RCTBorderSide)side
{
return _borderLayers[side].fillColor ?: self.layer.borderColor;
}
- (void)setBorderColor:(CGColorRef)color forSide:(RCTBorderSide)side
{
[self createShapeLayerIfNotExistsForSide:side].fillColor = color;
[self updateBorderLayers];
}
#define BORDER_COLOR(SIDE) \
- (CGColorRef)border##SIDE##Color { return [self borderColorForSide:RCTBorderSide##SIDE]; } \
- (void)setBorder##SIDE##Color:(CGColorRef)color { [self setBorderColor:color forSide:RCTBorderSide##SIDE]; }
BORDER_COLOR(Top)
BORDER_COLOR(Right)
BORDER_COLOR(Bottom)
BORDER_COLOR(Left)
- (void)setBorderWidth:(CGFloat)borderWidth
{
_borderWidth = borderWidth;
for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
_borderWidths[side] = borderWidth;
}
[self updateBorderLayers];
}
- (void)setBorderColor:(CGColorRef)borderColor
{
self.layer.borderColor = borderColor;
for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
_borderLayers[side].fillColor = borderColor;
}
[self updateBorderLayers];
}
- (CGColorRef)borderColor
{
return self.layer.borderColor;
} }
@end @end

View File

@ -81,9 +81,6 @@ RCT_REMAP_VIEW_PROPERTY(shadowColor, layer.shadowColor, CGColor);
RCT_REMAP_VIEW_PROPERTY(shadowOffset, layer.shadowOffset, CGSize); RCT_REMAP_VIEW_PROPERTY(shadowOffset, layer.shadowOffset, CGSize);
RCT_REMAP_VIEW_PROPERTY(shadowOpacity, layer.shadowOpacity, CGFloat) RCT_REMAP_VIEW_PROPERTY(shadowOpacity, layer.shadowOpacity, CGFloat)
RCT_REMAP_VIEW_PROPERTY(shadowRadius, layer.shadowRadius, CGFloat) RCT_REMAP_VIEW_PROPERTY(shadowRadius, layer.shadowRadius, CGFloat)
RCT_REMAP_VIEW_PROPERTY(borderColor, layer.borderColor, CGColor);
RCT_REMAP_VIEW_PROPERTY(borderRadius, layer.cornerRadius, CGFloat)
RCT_REMAP_VIEW_PROPERTY(borderWidth, layer.borderWidth, CGFloat)
RCT_REMAP_VIEW_PROPERTY(transformMatrix, layer.transform, CATransform3D) RCT_REMAP_VIEW_PROPERTY(transformMatrix, layer.transform, CATransform3D)
RCT_CUSTOM_VIEW_PROPERTY(overflow, css_overflow, RCTView) RCT_CUSTOM_VIEW_PROPERTY(overflow, css_overflow, RCTView)
{ {
@ -122,6 +119,42 @@ RCT_CUSTOM_VIEW_PROPERTY(removeClippedSubviews, BOOL, RCTView)
view.removeClippedSubviews = json ? [RCTConvert BOOL:json] : defaultView.removeClippedSubviews; view.removeClippedSubviews = json ? [RCTConvert BOOL:json] : defaultView.removeClippedSubviews;
} }
} }
RCT_REMAP_VIEW_PROPERTY(borderRadius, layer.cornerRadius, CGFloat)
RCT_CUSTOM_VIEW_PROPERTY(borderColor, CGColor, RCTView)
{
if ([view respondsToSelector:@selector(setBorderColor:)]) {
view.borderColor = json ? [RCTConvert CGColor:json] : defaultView.borderColor;
} else {
view.layer.borderColor = json ? [RCTConvert CGColor:json] : defaultView.layer.borderColor;
}
}
RCT_CUSTOM_VIEW_PROPERTY(borderWidth, CGFloat, RCTView)
{
if ([view respondsToSelector:@selector(setBorderWidth:)]) {
view.borderWidth = json ? [RCTConvert CGFloat:json] : defaultView.borderWidth;
} else {
view.layer.borderWidth = json ? [RCTConvert CGFloat:json] : defaultView.layer.borderWidth;
}
}
#define RCT_VIEW_BORDER_PROPERTY(SIDE) \
RCT_CUSTOM_VIEW_PROPERTY(border##SIDE##Width, CGFloat, RCTView) \
{ \
if ([view respondsToSelector:@selector(setBorder##SIDE##Width:)]) { \
view.border##SIDE##Width = json ? [RCTConvert CGFloat:json] : defaultView.border##SIDE##Width; \
} \
} \
RCT_CUSTOM_VIEW_PROPERTY(border##SIDE##Color, UIColor, RCTView) \
{ \
if ([view respondsToSelector:@selector(setBorder##SIDE##Color:)]) { \
view.border##SIDE##Color = json ? [RCTConvert CGColor:json] : defaultView.border##SIDE##Color; \
} \
}
RCT_VIEW_BORDER_PROPERTY(Top)
RCT_VIEW_BORDER_PROPERTY(Right)
RCT_VIEW_BORDER_PROPERTY(Bottom)
RCT_VIEW_BORDER_PROPERTY(Left)
#pragma mark - ShadowView properties #pragma mark - ShadowView properties
@ -169,15 +202,4 @@ RCT_CUSTOM_SHADOW_PROPERTY(backgroundColor, UIColor, RCTShadowView)
view.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet; view.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
} }
// Border properties - to be deprecated
RCT_REMAP_VIEW_PROPERTY(borderTopWidth, reactBorderTop.width, CGFloat);
RCT_REMAP_VIEW_PROPERTY(borderRightWidth, reactBorderRight.width, CGFloat);
RCT_REMAP_VIEW_PROPERTY(borderBottomWidth, reactBorderBottom.width, CGFloat);
RCT_REMAP_VIEW_PROPERTY(borderLeftWidth, reactBorderLeft.width, CGFloat);
RCT_REMAP_VIEW_PROPERTY(borderTopColor, reactBorderTop.color, UIColor);
RCT_REMAP_VIEW_PROPERTY(borderRightColor, reactBorderRight.color, UIColor);
RCT_REMAP_VIEW_PROPERTY(borderBottomColor, reactBorderBottom.color, UIColor);
RCT_REMAP_VIEW_PROPERTY(borderLeftColor, reactBorderLeft.color, UIColor);
@end @end

View File

@ -42,12 +42,3 @@
- (BOOL)reactRespondsToTouch:(UITouch *)touch; - (BOOL)reactRespondsToTouch:(UITouch *)touch;
@end @end
@interface UIView (ReactKitBorders)
/**
* Borders stuff - pay no attention to this, it's going away (#6548297)
*/
- (void)reactSetBorders;
@end

View File

@ -119,156 +119,3 @@
} }
@end @end
#pragma mark - Borders
// Note: the value of this enum determines their relative zPosition
typedef NS_ENUM(NSUInteger, RCTBorderSide) {
RCTBorderSideTop = 0,
RCTBorderSideRight = 1,
RCTBorderSideBottom = 2,
RCTBorderSideLeft = 3
};
@interface RCTSingleSidedBorder : NSObject
@property (nonatomic, readwrite, assign) CGFloat width;
@property (nonatomic, readwrite, strong) UIColor *color;
@property (nonatomic, readonly, assign) RCTBorderSide side;
- (instancetype)initWithSide:(RCTBorderSide)side superlayer:(CALayer *)superlayer;
- (void)superLayerBoundsDidChange;
@end
@implementation RCTSingleSidedBorder
{
CALayer *_borderLayer;
}
- (instancetype)initWithSide:(RCTBorderSide)side superlayer:(CALayer *)superlayer
{
if (self = [super init]) {
_side = side;
_borderLayer = [CALayer layer];
_borderLayer.delegate = self;
_borderLayer.zPosition = INT_MAX - _side;
[superlayer insertSublayer:_borderLayer atIndex:0];
}
return self;
}
- (void)dealloc
{
_borderLayer.delegate = nil;
}
- (void)setWidth:(CGFloat)width
{
_width = width;
[_borderLayer setNeedsLayout];
}
- (void)setColor:(UIColor *)color
{
_color = color;
_borderLayer.backgroundColor = _color.CGColor;
[_borderLayer setNeedsLayout];
}
- (void)superLayerBoundsDidChange
{
[_borderLayer setNeedsLayout];
}
#pragma mark - CALayerDelegate
- (void)layoutSublayersOfLayer:(CALayer *)layer
{
CGSize superlayerSize = layer.superlayer.frame.size;
CGFloat xPosition = 0.0f;
CGFloat yPosition = 0.0f;
// Note: we ensure side layers are below top & bottom for snapshot test consistency
switch (self.side) {
case RCTBorderSideTop:
layer.frame = CGRectMake(xPosition, yPosition, superlayerSize.width, self.width);
break;
case RCTBorderSideRight:
xPosition = superlayerSize.width - self.width;
layer.frame = CGRectMake(xPosition, yPosition, self.width, superlayerSize.height);
[layer.superlayer insertSublayer:layer atIndex:0];
break;
case RCTBorderSideBottom:
yPosition = superlayerSize.height - self.width;
layer.frame = CGRectMake(xPosition, yPosition, superlayerSize.width, self.width);
break;
case RCTBorderSideLeft:
layer.frame = CGRectMake(xPosition, yPosition, self.width, superlayerSize.height);
[layer.superlayer insertSublayer:layer atIndex:0];
break;
}
}
// Disable animations for layer
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
return (id)[NSNull null];
}
@end
@implementation UIView (ReactKitBorders)
- (void)reactSetBorders
{
NSMutableDictionary *borders = objc_getAssociatedObject(self, @selector(_createOrGetBorderWithSide:));
if (borders) {
for (RCTSingleSidedBorder *border in [borders allValues]) {
[border superLayerBoundsDidChange];
}
}
}
- (RCTSingleSidedBorder *)reactBorderTop
{
return [self _createOrGetBorderWithSide:RCTBorderSideTop];
}
- (RCTSingleSidedBorder *)reactBorderRight
{
return [self _createOrGetBorderWithSide:RCTBorderSideRight];
}
- (RCTSingleSidedBorder *)reactBorderBottom
{
return [self _createOrGetBorderWithSide:RCTBorderSideBottom];
}
- (RCTSingleSidedBorder *)reactBorderLeft
{
return [self _createOrGetBorderWithSide:RCTBorderSideLeft];
}
- (RCTSingleSidedBorder *)_createOrGetBorderWithSide:(RCTBorderSide)side
{
NSMutableDictionary *borders = objc_getAssociatedObject(self, _cmd);
if (!borders) {
borders = [[NSMutableDictionary alloc] init];
objc_setAssociatedObject(self, _cmd, borders, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
RCTSingleSidedBorder *border = [borders objectForKey:@(side)];
if (!border) {
border = [[RCTSingleSidedBorder alloc] initWithSide:side superlayer:self.layer];
[borders setObject:border forKey:@(side)];
}
return border;
}
@end