This commit is contained in:
Tadeu Zagallo 2015-05-29 23:18:30 +01:00
Родитель ef4f143ae0 bb7ae59a12
Коммит 1567e8224a
33 изменённых файлов: 751 добавлений и 427 удалений

Просмотреть файл

@ -81,11 +81,15 @@ var styles = StyleSheet.create({
borderTopLeftRadius: 100,
},
border7: {
borderRadius: 20,
borderWidth: 10,
borderColor: 'rgba(255,0,0,0.5)',
borderRadius: 30,
overflow: 'hidden',
},
border7_inner: {
backgroundColor: 'blue',
flex: 1,
width: 100,
height: 100
},
});

Просмотреть файл

@ -300,17 +300,17 @@ exports.examples = [
title: 'containerBackgroundColor attribute',
render: function() {
return (
<View>
<View style={{flexDirection: 'row', height: 85}}>
<View style={{backgroundColor: '#ffaaaa', width: 150}} />
<View style={{backgroundColor: '#aaaaff', width: 150}} />
<View style={{backgroundColor: 'yellow'}}>
<View style={{flexDirection: 'row', position: 'absolute', height: 80}}>
<View style={{backgroundColor: '#ffaaaa', width: 140}} />
<View style={{backgroundColor: '#aaaaff', width: 140}} />
</View>
<Text style={[styles.backgroundColorText, {top: -80}]}>
<Text style={styles.backgroundColorText}>
Default containerBackgroundColor (inherited) + backgroundColor wash
</Text>
<Text style={[
styles.backgroundColorText,
{top: -70, containerBackgroundColor: 'transparent'}]}>
{marginBottom: 5, containerBackgroundColor: 'transparent'}]}>
{"containerBackgroundColor: 'transparent' + backgroundColor wash"}
</Text>
</View>
@ -322,13 +322,13 @@ exports.examples = [
return (
<View>
<Text numberOfLines={1}>
Maximum of one line no matter now much I write here. If I keep writing it{"'"}ll just truncate after one line
Maximum of one line, no matter how much I write here. If I keep writing, it{"'"}ll just truncate after one line.
</Text>
<Text numberOfLines={2} style={{marginTop: 20}}>
Maximum of two lines no matter now much I write here. If I keep writing it{"'"}ll just truncate after two lines
Maximum of two lines, no matter how much I write here. If I keep writing, it{"'"}ll just truncate after two lines.
</Text>
<Text style={{marginTop: 20}}>
No maximum lines specified no matter now much I write here. If I keep writing it{"'"}ll just keep going and going
No maximum lines specified, no matter how much I write here. If I keep writing, it{"'"}ll just keep going and going.
</Text>
</View>
);
@ -337,7 +337,8 @@ exports.examples = [
var styles = StyleSheet.create({
backgroundColorText: {
left: 5,
margin: 5,
marginBottom: 0,
backgroundColor: 'rgba(100, 100, 100, 0.3)'
},
entity: {

Просмотреть файл

@ -77,13 +77,7 @@ var styles = StyleSheet.create({
paddingHorizontal: 10,
paddingVertical: 5,
},
titleRow: {
flexDirection: 'row',
justifyContent: 'space-between',
backgroundColor: 'transparent',
},
titleText: {
backgroundColor: 'transparent',
fontSize: 14,
fontWeight: '500',
},
@ -101,8 +95,7 @@ var styles = StyleSheet.create({
height: 8,
},
children: {
backgroundColor: 'transparent',
padding: 10,
margin: 10,
}
});

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 72 KiB

После

Ширина:  |  Высота:  |  Размер: 72 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 87 KiB

После

Ширина:  |  Высота:  |  Размер: 87 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 270 KiB

После

Ширина:  |  Высота:  |  Размер: 269 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 86 KiB

После

Ширина:  |  Высота:  |  Размер: 86 KiB

Просмотреть файл

@ -38,7 +38,7 @@
RCTAssert(!__LP64__, @"Snapshot tests should be run on 32-bit device simulators (e.g. iPhone 5)");
#endif
NSString *version = [[UIDevice currentDevice] systemVersion];
RCTAssert([version isEqualToString:@"8.1"], @"Snapshot tests should be run on iOS 8.1, found %@", version);
RCTAssert([version isEqualToString:@"8.3"], @"Snapshot tests should be run on iOS 8.3, found %@", version);
_runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerApp");
// If tests have changes, set recordMode = YES below and run the affected

Просмотреть файл

@ -407,15 +407,23 @@ var TextInput = React.createClass({
}
},
getChildContext: function() {
return {isInAParentText: true};
},
childContextTypes: {
isInAParentText: React.PropTypes.bool
},
render: function() {
if (Platform.OS === 'ios') {
return this._renderIOs();
return this._renderIOS();
} else if (Platform.OS === 'android') {
return this._renderAndroid();
}
},
_renderIOs: function() {
_renderIOS: function() {
var textContainer;
var autoCapitalize = autoCapitalizeConsts[this.props.autoCapitalize];
@ -515,7 +523,8 @@ var TextInput = React.createClass({
return (
<TouchableWithoutFeedback
onPress={this._onPress}
rejectResponderTermination={true}>
rejectResponderTermination={true}
testID={this.props.testID}>
{textContainer}
</TouchableWithoutFeedback>
);
@ -523,6 +532,16 @@ var TextInput = React.createClass({
_renderAndroid: function() {
var autoCapitalize = autoCapitalizeConsts[this.props.autoCapitalize];
var children = this.props.children;
var childCount = 0;
ReactChildren.forEach(children, () => ++childCount);
invariant(
!(this.props.value && childCount),
'Cannot specify both value and children.'
);
if (childCount > 1) {
children = <Text>{children}</Text>;
}
var textContainer =
<AndroidTextInput
ref="input"
@ -540,6 +559,7 @@ var TextInput = React.createClass({
password={this.props.password || this.props.secureTextEntry}
placeholder={this.props.placeholder}
text={this.state.bufferedValue}
children={children}
/>;
return (

Просмотреть файл

@ -549,11 +549,14 @@ var Navigator = React.createClass({
this.spring.getCurrentValue()
);
} else if (this.state.activeGesture != null) {
this._transitionBetween(
this.state.presentedIndex,
this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture),
this.spring.getCurrentValue()
);
var presentedToIndex = this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture);
if (presentedToIndex > -1) {
this._transitionBetween(
this.state.presentedIndex,
presentedToIndex,
this.spring.getCurrentValue()
);
}
}
},

Просмотреть файл

@ -75,7 +75,9 @@ var InspectorOverlay = React.createClass({
var ElementProperties = React.createClass({
render: function() {
var path = this.props.hierarchy.map((instance) => instance.getName()).join(' > ');
var path = this.props.hierarchy.map((instance) => {
return instance.getName ? instance.getName() : 'Unknown';
}).join(' > ');
return (
<View style={styles.info}>
<Text style={styles.path}>

Просмотреть файл

@ -29,4 +29,7 @@ var ReactNativeStyleAttributes = {
ReactNativeStyleAttributes.transformMatrix = { diff: matricesDiffer };
ReactNativeStyleAttributes.shadowOffset = { diff: sizesDiffer };
// Do not rely on this attribute.
ReactNativeStyleAttributes.decomposedMatrix = 'decomposedMatrix';
module.exports = ReactNativeStyleAttributes;

Просмотреть файл

@ -29,5 +29,6 @@ extern NSString *const RCTReactTagAttributeName;
@property (nonatomic, assign) NSWritingDirection writingDirection;
- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width;
- (void)recomputeText;
@end

Просмотреть файл

@ -72,6 +72,12 @@ static css_dim_t RCTMeasure(void *context, float width)
return textStorage;
}
- (void)recomputeText
{
[self attributedString];
[self setTextComputed];
}
- (NSAttributedString *)attributedString
{
return [self _attributedStringWithFontFamily:nil
@ -125,13 +131,13 @@ static css_dim_t RCTMeasure(void *context, float width)
}
if (_color) {
[self _addAttribute:NSForegroundColorAttributeName withValue:self.color toAttributedString:attributedString];
[self _addAttribute:NSForegroundColorAttributeName withValue:_color toAttributedString:attributedString];
}
if (_isHighlighted) {
[self _addAttribute:RCTIsHighlightedAttributeName withValue:@YES toAttributedString:attributedString];
}
if (_textBackgroundColor) {
[self _addAttribute:NSBackgroundColorAttributeName withValue:self.textBackgroundColor toAttributedString:attributedString];
[self _addAttribute:NSBackgroundColorAttributeName withValue:_textBackgroundColor toAttributedString:attributedString];
}
UIFont *font = [RCTConvert UIFont:nil withFamily:fontFamily size:fontSize weight:fontWeight style:fontStyle];

Просмотреть файл

@ -16,12 +16,14 @@
@implementation RCTText
{
NSTextStorage *_textStorage;
NSMutableArray *_reactSubviews;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
_textStorage = [[NSTextStorage alloc] init];
_reactSubviews = [NSMutableArray array];
self.isAccessibilityElement = YES;
self.accessibilityTraits |= UIAccessibilityTraitStaticText;
@ -32,6 +34,30 @@
return self;
}
- (void)reactSetFrame:(CGRect)frame
{
// Text looks super weird if its frame is animated.
// This disables the frame animation, without affecting opacity, etc.
[UIView performWithoutAnimation:^{
[super reactSetFrame:frame];
}];
}
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
{
[_reactSubviews insertObject:subview atIndex:atIndex];
}
- (void)removeReactSubview:(UIView *)subview
{
[_reactSubviews removeObject:subview];
}
- (NSMutableArray *)reactSubviews
{
return _reactSubviews;
}
- (void)setTextStorage:(NSTextStorage *)textStorage
{
_textStorage = textStorage;

Просмотреть файл

@ -34,6 +34,7 @@ RCT_EXPORT_MODULE()
#pragma mark - View properties
RCT_IGNORE_VIEW_PROPERTY(backgroundColor);
RCT_REMAP_VIEW_PROPERTY(containerBackgroundColor, backgroundColor, UIColor)
#pragma mark - Shadow properties
@ -55,8 +56,6 @@ RCT_EXPORT_SHADOW_PROPERTY(numberOfLines, NSUInteger)
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry
{
NSMutableArray *uiBlocks = [NSMutableArray new];
for (RCTShadowView *rootView in shadowViewRegistry.allObjects) {
if (![rootView isReactRootView]) {
// This isn't a root view
@ -68,16 +67,13 @@ RCT_EXPORT_SHADOW_PROPERTY(numberOfLines, NSUInteger)
continue;
}
RCTSparseArray *reactTaggedTextStorage = [[RCTSparseArray alloc] init];
NSMutableArray *queue = [NSMutableArray arrayWithObject:rootView];
for (NSInteger i = 0; i < [queue count]; i++) {
RCTShadowView *shadowView = queue[i];
RCTAssert([shadowView isTextDirty], @"Don't process any nodes that don't have dirty text");
if ([shadowView isKindOfClass:[RCTShadowText class]]) {
RCTShadowText *shadowText = (RCTShadowText *)shadowView;
NSTextStorage *textStorage = [shadowText buildTextStorageForWidth:shadowView.frame.size.width];
reactTaggedTextStorage[shadowText.reactTag] = textStorage;
[(RCTShadowText *)shadowView recomputeText];
} else if ([shadowView isKindOfClass:[RCTShadowRawText class]]) {
RCTLogError(@"Raw text cannot be used outside of a <Text> tag. Not rendering string: '%@'",
[(RCTShadowRawText *)shadowView text]);
@ -91,30 +87,21 @@ RCT_EXPORT_SHADOW_PROPERTY(numberOfLines, NSUInteger)
[shadowView setTextComputed];
}
[uiBlocks addObject:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
[reactTaggedTextStorage enumerateObjectsUsingBlock:^(NSTextStorage *textStorage, NSNumber *reactTag, BOOL *stop) {
RCTText *text = viewRegistry[reactTag];
text.textStorage = textStorage;
}];
}];
}
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
for (RCTViewManagerUIBlock shadowBlock in uiBlocks) {
shadowBlock(uiManager, viewRegistry);
}
};
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {};
}
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowText *)shadowView
{
NSNumber *reactTag = shadowView.reactTag;
UIEdgeInsets padding = shadowView.paddingAsInsets;
NSTextStorage *textStorage = [shadowView buildTextStorageForWidth:shadowView.frame.size.width];
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
RCTText *text = viewRegistry[reactTag];
text.contentInset = padding;
text.textStorage = textStorage;
};
}

Просмотреть файл

@ -12,7 +12,9 @@
'use strict';
var NativeMethodsMixin = require('NativeMethodsMixin');
var Platform = require('Platform');
var React = require('React');
var ReactInstanceMap = require('ReactInstanceMap');
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
var StyleSheetPropType = require('StyleSheetPropType');
var TextStylePropTypes = require('TextStylePropTypes');
@ -177,6 +179,14 @@ var Text = React.createClass({
return PRESS_RECT_OFFSET;
},
getChildContext: function() {
return {isInAParentText: true};
},
childContextTypes: {
isInAParentText: React.PropTypes.bool
},
render: function() {
var props = {};
for (var key in this.props) {
@ -194,7 +204,14 @@ var Text = React.createClass({
props.onResponderMove = this.handleResponderMove;
props.onResponderRelease = this.handleResponderRelease;
props.onResponderTerminate = this.handleResponderTerminate;
return <RCTText {...props} />;
// TODO: Switch to use contextTypes and this.context after React upgrade
var context = ReactInstanceMap.get(this)._context;
if (context.isInAParentText) {
return <RCTVirtualText {...props} />;
} else {
return <RCTText {...props} />;
}
},
});
@ -208,5 +225,15 @@ type RectOffset = {
var PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
var RCTText = createReactNativeComponentClass(viewConfig);
var RCTVirtualText = RCTText;
if (Platform.OS === 'android') {
RCTVirtualText = createReactNativeComponentClass({
validAttributes: merge(ReactNativeViewAttributes.UIView, {
isHighlighted: true,
}),
uiViewClassName: 'RCTVirtualText',
});
}
module.exports = Text;

Просмотреть файл

@ -109,8 +109,8 @@ typedef NSArray CGColorArray;
typedef id NSPropertyList;
+ (NSPropertyList)NSPropertyList:(id)json;
typedef BOOL css_overflow;
+ (css_overflow)css_overflow:(id)json;
typedef BOOL css_clip_t;
+ (css_clip_t)css_clip_t:(id)json;
+ (css_flex_direction_t)css_flex_direction_t:(id)json;
+ (css_justify_t)css_justify_t:(id)json;
+ (css_align_t)css_align_t:(id)json;

Просмотреть файл

@ -916,10 +916,10 @@ static id RCTConvertPropertyListValue(id json)
return RCTConvertPropertyListValue(json);
}
RCT_ENUM_CONVERTER(css_overflow, (@{
@"hidden": @NO,
@"visible": @YES
}), YES, boolValue)
RCT_ENUM_CONVERTER(css_clip_t, (@{
@"hidden": @YES,
@"visible": @NO
}), NO, boolValue)
RCT_ENUM_CONVERTER(css_flex_direction_t, (@{
@"row": @(CSS_FLEX_DIRECTION_ROW),

Просмотреть файл

@ -58,6 +58,7 @@
[_rootView addSubview:_stackTraceTableView];
UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeCustom];
dismissButton.accessibilityIdentifier = @"redbox-dismiss";
dismissButton.titleLabel.font = [UIFont systemFontOfSize:14];
[dismissButton setTitle:@"Dismiss (ESC)" forState:UIControlStateNormal];
[dismissButton setTitleColor:[[UIColor whiteColor] colorWithAlphaComponent:0.5] forState:UIControlStateNormal];
@ -65,6 +66,7 @@
[dismissButton addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside];
UIButton *reloadButton = [UIButton buttonWithType:UIButtonTypeCustom];
reloadButton.accessibilityIdentifier = @"redbox-reload";
reloadButton.titleLabel.font = [UIFont systemFontOfSize:14];
[reloadButton setTitle:@"Reload JS (\u2318R)" forState:UIControlStateNormal];
[reloadButton setTitleColor:[[UIColor whiteColor] colorWithAlphaComponent:0.5] forState:UIControlStateNormal];
@ -172,6 +174,7 @@
{
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"msg-cell"];
cell.textLabel.accessibilityIdentifier = @"redbox-error";
cell.textLabel.textColor = [UIColor whiteColor];
cell.textLabel.font = [UIFont boldSystemFontOfSize:16];
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;

Просмотреть файл

@ -11,6 +11,18 @@
#import "RCTBridge.h"
/**
* This notification is sent when the first subviews are added to the root view
* after the application has loaded. This is used to hide the `loadingView`, and
* is a good indicator that the application is ready to use.
*/
extern NSString *const RCTContentDidAppearNotification;
/**
* Native view used to host React-managed views within the app. Can be used just
* like any ordinary UIView. You can have multiple RCTRootViews on screen at
* once, all controlled by the same JavaScript application.
*/
@interface RCTRootView : UIView
/**
@ -67,4 +79,18 @@
*/
@property (nonatomic, strong, readonly) UIView *contentView;
/**
* A view to display while the JavaScript is loading, so users aren't presented
* with a blank screen. By default this is nil, but you can override it with
* (for example) a UIActivityIndicatorView or a placeholder image.
*/
@property (nonatomic, strong) UIView *loadingView;
/**
* Timings for hiding the loading view after the content has loaded. Both of
* these values default to 0.25 seconds.
*/
@property (nonatomic, assign) NSTimeInterval loadingViewFadeDelay;
@property (nonatomic, assign) NSTimeInterval loadingViewFadeDuration;
@end

Просмотреть файл

@ -25,6 +25,8 @@
#import "RCTWebViewExecutor.h"
#import "UIView+React.h"
NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotification";
@interface RCTBridge (RCTRootView)
@property (nonatomic, weak, readonly) RCTBridge *batchedBridge;
@ -39,6 +41,8 @@
@interface RCTRootContentView : RCTView <RCTInvalidating>
@property (nonatomic, readonly) BOOL contentHasAppeared;
- (instancetype)initWithFrame:(CGRect)frame bridge:(RCTBridge *)bridge;
@end
@ -64,14 +68,23 @@
_bridge = bridge;
_moduleName = moduleName;
_loadingViewFadeDelay = 0.25;
_loadingViewFadeDuration = 0.25;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(javaScriptDidLoad:)
name:RCTJavaScriptDidLoadNotification
object:_bridge];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(hideLoadingView)
name:RCTContentDidAppearNotification
object:self];
if (!_bridge.batchedBridge.isLoading) {
[self bundleFinishedLoading:_bridge.batchedBridge];
}
[self showLoadingView];
}
return self;
}
@ -106,6 +119,41 @@
RCT_IMPORT_METHOD(AppRegistry, runApplication)
RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
- (void)setLoadingView:(UIView *)loadingView
{
_loadingView = loadingView;
if (!_contentView.contentHasAppeared) {
[self showLoadingView];
}
}
- (void)showLoadingView
{
if (_loadingView && !_contentView.contentHasAppeared) {
_loadingView.hidden = NO;
[self addSubview:_loadingView];
}
}
- (void)hideLoadingView
{
if (_loadingView.superview == self && _contentView.contentHasAppeared) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_loadingViewFadeDelay * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
[UIView transitionWithView:self
duration:_loadingViewFadeDuration
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
_loadingView.hidden = YES;
} completion:^(BOOL finished) {
[_loadingView removeFromSuperview];
}];
});
}
}
- (void)javaScriptDidLoad:(NSNotification *)notification
{
RCTBridge *bridge = notification.userInfo[@"bridge"];
@ -119,35 +167,31 @@ RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
return;
}
/**
* Every root view that is created must have a unique React tag.
* Numbering of these tags goes from 1, 11, 21, 31, etc
*
* NOTE: Since the bridge persists, the RootViews might be reused, so now
* the React tag is assigned every time we load new content.
*/
[_contentView removeFromSuperview];
_contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
bridge:bridge];
_contentView.backgroundColor = self.backgroundColor;
[self addSubview:_contentView];
[self insertSubview:_contentView atIndex:0];
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@"rootTag": _contentView.reactTag,
@"initialProps": _initialProperties ?: @{},
};
[bridge enqueueJSCall:@"AppRegistry.runApplication"
args:@[moduleName, appParameters]];
args:@[moduleName, appParameters]];
});
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (_contentView) {
_contentView.frame = self.bounds;
}
_contentView.frame = self.bounds;
_loadingView.center = (CGPoint){
CGRectGetMidX(self.bounds),
CGRectGetMidY(self.bounds)
};
}
- (NSNumber *)reactTag
@ -155,6 +199,13 @@ RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
return _contentView.reactTag;
}
- (void)contentViewInvalidated
{
[_contentView removeFromSuperview];
_contentView = nil;
[self showLoadingView];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
@ -193,6 +244,18 @@ RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
return self;
}
- (void)insertReactSubview:(id<RCTViewNodeProtocol>)subview atIndex:(NSInteger)atIndex
{
[super insertReactSubview:subview atIndex:atIndex];
dispatch_async(dispatch_get_main_queue(), ^{
if (!_contentHasAppeared) {
_contentHasAppeared = YES;
[[NSNotificationCenter defaultCenter] postNotificationName:RCTContentDidAppearNotification
object:self.superview];
}
});
}
- (void)setFrame:(CGRect)frame
{
super.frame = frame;
@ -237,7 +300,7 @@ RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
{
if (self.isValid) {
self.userInteractionEnabled = NO;
[self removeFromSuperview];
[(RCTRootView *)self.superview contentViewInvalidated];
[_bridge enqueueJSCall:@"ReactNative.unmountComponentAtNodeAndRemoveContainer"
args:@[self.reactTag]];
}

Просмотреть файл

@ -420,12 +420,12 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
{
if ([NSThread currentThread] != _javaScriptThread) {
[self performSelector:@selector(executeBlockOnJavaScriptQueue:)
onThread:_javaScriptThread withObject:block waitUntilDone:NO];
} else {
block();
}
if ([NSThread currentThread] != _javaScriptThread) {
[self performSelector:@selector(executeBlockOnJavaScriptQueue:)
onThread:_javaScriptThread withObject:block waitUntilDone:NO];
} else {
block();
}
}
- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block

Просмотреть файл

@ -913,6 +913,13 @@ RCT_EXPORT_METHOD(findSubviewIn:(NSNumber *)reactTag atPoint:(CGPoint)point call
- (void)batchDidComplete
{
// Gather blocks to be executed now that all view hierarchy manipulations have
// been completed (note that these may still take place before layout has finished)
for (RCTViewManager *manager in _viewManagers.allValues) {
RCTViewManagerUIBlock uiBlock = [manager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
[self addUIBlock:uiBlock];
}
// Set up next layout animation
if (_nextLayoutAnimation) {
RCTLayoutAnimation *layoutAnimation = _nextLayoutAnimation;
@ -936,12 +943,6 @@ RCT_EXPORT_METHOD(findSubviewIn:(NSNumber *)reactTag atPoint:(CGPoint)point call
_nextLayoutAnimation = nil;
}
// Gather blocks to be executed now that layout is completed
for (RCTViewManager *manager in _viewManagers.allValues) {
RCTViewManagerUIBlock uiBlock = [manager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
[self addUIBlock:uiBlock];
}
[self flushUIBlocks];
}

Просмотреть файл

@ -39,6 +39,7 @@
13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */; };
13C156051AB1A2840079392D /* RCTWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156021AB1A2840079392D /* RCTWebView.m */; };
13C156061AB1A2840079392D /* RCTWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156041AB1A2840079392D /* RCTWebViewManager.m */; };
13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */ = {isa = PBXBuildFile; fileRef = 13CC8A811B17642100940AE7 /* RCTBorderDrawing.m */; };
13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067491A70F434002CDEE1 /* RCTUIManager.m */; };
13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674C1A70F44B002CDEE1 /* RCTShadowView.m */; };
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; };
@ -155,6 +156,8 @@
13C325261AA63B6A0048765F /* RCTAutoInsetsProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAutoInsetsProtocol.h; sourceTree = "<group>"; };
13C325271AA63B6A0048765F /* RCTScrollableProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTScrollableProtocol.h; sourceTree = "<group>"; };
13C325281AA63B6A0048765F /* RCTViewNodeProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewNodeProtocol.h; sourceTree = "<group>"; };
13CC8A801B17642100940AE7 /* RCTBorderDrawing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBorderDrawing.h; sourceTree = "<group>"; };
13CC8A811B17642100940AE7 /* RCTBorderDrawing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBorderDrawing.m; sourceTree = "<group>"; };
13E067481A70F434002CDEE1 /* RCTUIManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUIManager.h; sourceTree = "<group>"; };
13E067491A70F434002CDEE1 /* RCTUIManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManager.m; sourceTree = "<group>"; };
13E0674B1A70F44B002CDEE1 /* RCTShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTShadowView.h; sourceTree = "<group>"; };
@ -279,6 +282,8 @@
children = (
13442BF21AA90E0B0037E5B0 /* RCTAnimationType.h */,
13C325261AA63B6A0048765F /* RCTAutoInsetsProtocol.h */,
13CC8A801B17642100940AE7 /* RCTBorderDrawing.h */,
13CC8A811B17642100940AE7 /* RCTBorderDrawing.m */,
58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */,
58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */,
13456E911ADAD2DE009F94A7 /* RCTConvert+CoreLocation.h */,
@ -513,6 +518,7 @@
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */,
13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */,
83CBBA5A1A601E9000E9B192 /* RCTRedBox.m in Sources */,
13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */,
83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */,
13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */,
58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */,

Просмотреть файл

@ -0,0 +1,61 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
typedef struct {
CGFloat topLeft;
CGFloat topRight;
CGFloat bottomLeft;
CGFloat bottomRight;
} RCTCornerRadii;
typedef struct {
CGSize topLeft;
CGSize topRight;
CGSize bottomLeft;
CGSize bottomRight;
} RCTCornerInsets;
typedef struct {
CGColorRef top;
CGColorRef left;
CGColorRef bottom;
CGColorRef right;
} RCTBorderColors;
/**
* Determine if the border widths, colors and radii are all equal.
*/
BOOL RCTBorderInsetsAreEqual(UIEdgeInsets borderInsets);
BOOL RCTCornerRadiiAreEqual(RCTCornerRadii cornerRadii);
BOOL RCTBorderColorsAreEqual(RCTBorderColors borderColors);
/**
* Convert RCTCornerRadii to RCTCornerInsets by applying border insets.
*/
RCTCornerInsets RCTGetCornerInsets(RCTCornerRadii cornerRadii,
UIEdgeInsets borderInsets);
/**
* Create a CGPath representing a rounded rectangle with the specified bounds
* and corner insets. Note that the CGPathRef must be released by the caller.
*/
CGPathRef RCTPathCreateWithRoundedRect(CGRect bounds,
RCTCornerInsets cornerInsets,
const CGAffineTransform *transform);
/**
* Draw a CSS-compliant border as a scalable image.
*/
UIImage *RCTGetBorderImage(RCTCornerRadii cornerRadii,
UIEdgeInsets borderInsets,
RCTBorderColors borderColors,
CGColorRef backgroundColor,
BOOL drawToEdge);

Просмотреть файл

@ -0,0 +1,331 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTBorderDrawing.h"
static const CGFloat RCTViewBorderThreshold = 0.001;
BOOL RCTBorderInsetsAreEqual(UIEdgeInsets borderInsets)
{
return
ABS(borderInsets.left - borderInsets.right) < RCTViewBorderThreshold &&
ABS(borderInsets.left - borderInsets.bottom) < RCTViewBorderThreshold &&
ABS(borderInsets.left - borderInsets.top) < RCTViewBorderThreshold;
}
BOOL RCTCornerRadiiAreEqual(RCTCornerRadii cornerRadii)
{
return
ABS(cornerRadii.topLeft - cornerRadii.topRight) < RCTViewBorderThreshold &&
ABS(cornerRadii.topLeft - cornerRadii.bottomLeft) < RCTViewBorderThreshold &&
ABS(cornerRadii.topLeft - cornerRadii.bottomRight) < RCTViewBorderThreshold;
}
BOOL RCTBorderColorsAreEqual(RCTBorderColors borderColors)
{
return
CGColorEqualToColor(borderColors.left, borderColors.right) &&
CGColorEqualToColor(borderColors.left, borderColors.top) &&
CGColorEqualToColor(borderColors.left, borderColors.bottom);
}
RCTCornerInsets RCTGetCornerInsets(RCTCornerRadii cornerRadii,
UIEdgeInsets edgeInsets)
{
return (RCTCornerInsets) {
{
MAX(0, cornerRadii.topLeft - edgeInsets.left),
MAX(0, cornerRadii.topLeft - edgeInsets.top),
},
{
MAX(0, cornerRadii.topRight - edgeInsets.right),
MAX(0, cornerRadii.topRight - edgeInsets.top),
},
{
MAX(0, cornerRadii.bottomLeft - edgeInsets.left),
MAX(0, cornerRadii.bottomLeft - edgeInsets.bottom),
},
{
MAX(0, cornerRadii.bottomRight - edgeInsets.right),
MAX(0, cornerRadii.bottomRight - edgeInsets.bottom),
}
};
}
static void RCTPathAddEllipticArc(CGMutablePathRef path,
const CGAffineTransform *m,
CGPoint origin,
CGSize size,
CGFloat startAngle,
CGFloat endAngle,
BOOL clockwise)
{
CGFloat xScale = 1, yScale = 1, radius = 0;
if (size.width != 0) {
xScale = 1;
yScale = size.height / size.width;
radius = size.width;
} else if (size.height != 0) {
xScale = size.width / size.height;
yScale = 1;
radius = size.height;
}
CGAffineTransform t = CGAffineTransformMakeTranslation(origin.x, origin.y);
t = CGAffineTransformScale(t, xScale, yScale);
if (m != NULL) {
t = CGAffineTransformConcat(t, *m);
}
CGPathAddArc(path, &t, 0, 0, radius, startAngle, endAngle, clockwise);
}
CGPathRef RCTPathCreateWithRoundedRect(CGRect bounds,
RCTCornerInsets cornerInsets,
const CGAffineTransform *transform)
{
const CGFloat minX = CGRectGetMinX(bounds);
const CGFloat minY = CGRectGetMinY(bounds);
const CGFloat maxX = CGRectGetMaxX(bounds);
const CGFloat maxY = CGRectGetMaxY(bounds);
const CGSize topLeft = cornerInsets.topLeft;
const CGSize topRight = cornerInsets.topRight;
const CGSize bottomLeft = cornerInsets.bottomLeft;
const CGSize bottomRight = cornerInsets.bottomRight;
CGMutablePathRef path = CGPathCreateMutable();
RCTPathAddEllipticArc(path, transform, (CGPoint){
minX + topLeft.width, minY + topLeft.height
}, topLeft, M_PI, 3 * M_PI_2, NO);
RCTPathAddEllipticArc(path, transform, (CGPoint){
maxX - topRight.width, minY + topRight.height
}, topRight, 3 * M_PI_2, 0, NO);
RCTPathAddEllipticArc(path, transform, (CGPoint){
maxX - bottomRight.width, maxY - bottomRight.height
}, bottomRight, 0, M_PI_2, NO);
RCTPathAddEllipticArc(path, transform, (CGPoint){
minX + bottomLeft.width, maxY - bottomLeft.height
}, bottomLeft, M_PI_2, M_PI, NO);
CGPathCloseSubpath(path);
return path;
}
static void RCTEllipseGetIntersectionsWithLine(CGRect ellipseBounds,
CGPoint lineStart,
CGPoint lineEnd,
CGPoint intersections[2])
{
const CGPoint ellipseCenter = {
CGRectGetMidX(ellipseBounds),
CGRectGetMidY(ellipseBounds)
};
lineStart.x -= ellipseCenter.x;
lineStart.y -= ellipseCenter.y;
lineEnd.x -= ellipseCenter.x;
lineEnd.y -= ellipseCenter.y;
const CGFloat m = (lineEnd.y - lineStart.y) / (lineEnd.x - lineStart.x);
const CGFloat a = ellipseBounds.size.width / 2;
const CGFloat b = ellipseBounds.size.height / 2;
const CGFloat c = lineStart.y - m * lineStart.x;
const CGFloat A = (b * b + a * a * m * m);
const CGFloat B = 2 * a * a * c * m;
const CGFloat D = sqrt((a * a * (b * b - c * c)) / A + pow(B / (2 * A), 2));
const CGFloat x_ = -B / (2 * A);
const CGFloat x1 = x_ + D;
const CGFloat x2 = x_ - D;
const CGFloat y1 = m * x1 + c;
const CGFloat y2 = m * x2 + c;
intersections[0] = (CGPoint){x1 + ellipseCenter.x, y1 + ellipseCenter.y};
intersections[1] = (CGPoint){x2 + ellipseCenter.x, y2 + ellipseCenter.y};
}
UIImage *RCTGetBorderImage(RCTCornerRadii cornerRadii,
UIEdgeInsets borderInsets,
RCTBorderColors borderColors,
CGColorRef backgroundColor,
BOOL drawToEdge)
{
const BOOL hasCornerRadii =
cornerRadii.topLeft > RCTViewBorderThreshold ||
cornerRadii.topRight > RCTViewBorderThreshold ||
cornerRadii.bottomLeft > RCTViewBorderThreshold ||
cornerRadii.bottomRight > RCTViewBorderThreshold;
const RCTCornerInsets cornerInsets = RCTGetCornerInsets(cornerRadii, borderInsets);
const UIEdgeInsets edgeInsets = (UIEdgeInsets){
borderInsets.top + MAX(cornerInsets.topLeft.height, cornerInsets.topRight.height),
borderInsets.left + MAX(cornerInsets.topLeft.width, cornerInsets.bottomLeft.width),
borderInsets.bottom + MAX(cornerInsets.bottomLeft.height, cornerInsets.bottomRight.height),
borderInsets.right + MAX(cornerInsets.bottomRight.width, cornerInsets.topRight.width)
};
const CGSize size = (CGSize){
edgeInsets.left + 1 + edgeInsets.right,
edgeInsets.top + 1 + edgeInsets.bottom
};
const CGFloat alpha = CGColorGetAlpha(backgroundColor);
const BOOL opaque = (drawToEdge || !hasCornerRadii) && alpha == 1.0;
UIGraphicsBeginImageContextWithOptions(size, opaque, 0.0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
const CGRect rect = {.size = size};
CGPathRef path;
if (drawToEdge) {
path = CGPathCreateWithRect(rect, NULL);
} else {
path = RCTPathCreateWithRoundedRect(rect, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL);
}
if (backgroundColor) {
CGContextSetFillColorWithColor(ctx, backgroundColor);
CGContextAddPath(ctx, path);
CGContextFillPath(ctx);
}
CGContextAddPath(ctx, path);
CGPathRelease(path);
CGPathRef insetPath = RCTPathCreateWithRoundedRect(UIEdgeInsetsInsetRect(rect, borderInsets), cornerInsets, NULL);
CGContextAddPath(ctx, insetPath);
CGContextEOClip(ctx);
BOOL hasEqualColors = RCTBorderColorsAreEqual(borderColors);
if ((drawToEdge || !hasCornerRadii) && hasEqualColors) {
CGContextSetFillColorWithColor(ctx, borderColors.left);
CGContextAddRect(ctx, rect);
CGContextAddPath(ctx, insetPath);
CGContextEOFillPath(ctx);
} else {
CGPoint topLeft = (CGPoint){borderInsets.left, borderInsets.top};
if (cornerInsets.topLeft.width > 0 && cornerInsets.topLeft.height > 0) {
CGPoint points[2];
RCTEllipseGetIntersectionsWithLine((CGRect){
topLeft, {2 * cornerInsets.topLeft.width, 2 * cornerInsets.topLeft.height}
}, CGPointZero, topLeft, points);
if (!isnan(points[1].x) && !isnan(points[1].y)) {
topLeft = points[1];
}
}
CGPoint bottomLeft = (CGPoint){borderInsets.left, size.height - borderInsets.bottom};
if (cornerInsets.bottomLeft.width > 0 && cornerInsets.bottomLeft.height > 0) {
CGPoint points[2];
RCTEllipseGetIntersectionsWithLine((CGRect){
{bottomLeft.x, bottomLeft.y - 2 * cornerInsets.bottomLeft.height},
{2 * cornerInsets.bottomLeft.width, 2 * cornerInsets.bottomLeft.height}
}, (CGPoint){0, size.height}, bottomLeft, points);
if (!isnan(points[1].x) && !isnan(points[1].y)) {
bottomLeft = points[1];
}
}
CGPoint topRight = (CGPoint){size.width - borderInsets.right, borderInsets.top};
if (cornerInsets.topRight.width > 0 && cornerInsets.topRight.height > 0) {
CGPoint points[2];
RCTEllipseGetIntersectionsWithLine((CGRect){
{topRight.x - 2 * cornerInsets.topRight.width, topRight.y},
{2 * cornerInsets.topRight.width, 2 * cornerInsets.topRight.height}
}, (CGPoint){size.width, 0}, topRight, points);
if (!isnan(points[0].x) && !isnan(points[0].y)) {
topRight = points[0];
}
}
CGPoint bottomRight = (CGPoint){size.width - borderInsets.right, size.height - borderInsets.bottom};
if (cornerInsets.bottomRight.width > 0 && cornerInsets.bottomRight.height > 0) {
CGPoint points[2];
RCTEllipseGetIntersectionsWithLine((CGRect){
{bottomRight.x - 2 * cornerInsets.bottomRight.width, bottomRight.y - 2 * cornerInsets.bottomRight.height},
{2 * cornerInsets.bottomRight.width, 2 * cornerInsets.bottomRight.height}
}, (CGPoint){size.width, size.height}, bottomRight, points);
if (!isnan(points[0].x) && !isnan(points[0].y)) {
bottomRight = points[0];
}
}
// RIGHT
if (borderInsets.right > 0) {
const CGPoint points[] = {
(CGPoint){size.width, 0},
topRight,
bottomRight,
(CGPoint){size.width, size.height},
};
CGContextSetFillColorWithColor(ctx, borderColors.right);
CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
CGContextFillPath(ctx);
}
// BOTTOM
if (borderInsets.bottom > 0) {
const CGPoint points[] = {
(CGPoint){0, size.height},
bottomLeft,
bottomRight,
(CGPoint){size.width, size.height},
};
CGContextSetFillColorWithColor(ctx, borderColors.bottom);
CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
CGContextFillPath(ctx);
}
// LEFT
if (borderInsets.left > 0) {
const CGPoint points[] = {
CGPointZero,
topLeft,
bottomLeft,
(CGPoint){0, size.height},
};
CGContextSetFillColorWithColor(ctx, borderColors.left);
CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
CGContextFillPath(ctx);
}
// TOP
if (borderInsets.top > 0) {
const CGPoint points[] = {
CGPointZero,
topLeft,
topRight,
(CGPoint){size.width, 0},
};
CGContextSetFillColorWithColor(ctx, borderColors.top);
CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
CGContextFillPath(ctx);
}
}
CGPathRelease(insetPath);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return [image resizableImageWithCapInsets:edgeInsets];
}

Просмотреть файл

@ -13,6 +13,7 @@
#import "RCTLog.h"
#import "RCTSparseArray.h"
#import "RCTUtils.h"
#import "UIView+React.h"
typedef void (^RCTActionBlock)(RCTShadowView *shadowViewSelf, id value);
typedef void (^RCTResetActionBlock)(RCTShadowView *shadowViewSelf);
@ -38,7 +39,6 @@ typedef enum {
NSMutableArray *_reactSubviews;
BOOL _recomputePadding;
BOOL _recomputeMargin;
BOOL _isBGColorExplicitlySet;
float _paddingMetaProps[META_PROP_COUNT];
float _marginMetaProps[META_PROP_COUNT];
}
@ -167,22 +167,20 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
- (NSDictionary *)processBackgroundColor:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties
{
if (!_isBGColorExplicitlySet) {
if (!_backgroundColor) {
UIColor *parentBackgroundColor = parentProperties[RCTBackgroundColorProp];
if (parentBackgroundColor && ![_backgroundColor isEqual:parentBackgroundColor]) {
_backgroundColor = parentBackgroundColor;
if (parentBackgroundColor) {
[applierBlocks addObject:^(RCTSparseArray *viewRegistry) {
UIView *view = viewRegistry[_reactTag];
view.backgroundColor = parentBackgroundColor;
[view reactSetInheritedBackgroundColor:parentBackgroundColor];
}];
}
}
if (_isBGColorExplicitlySet) {
} else {
// Update parent properties for children
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:parentProperties];
CGFloat alpha = CGColorGetAlpha(_backgroundColor.CGColor);
if (alpha < 1.0) {
// If we see partial transparency, start propagating full transparency
// If bg is non-opaque, don't propagate further
properties[RCTBackgroundColorProp] = [UIColor clearColor];
} else {
properties[RCTBackgroundColorProp] = _backgroundColor;
@ -516,7 +514,6 @@ RCT_STYLE_PROPERTY(FlexWrap, flexWrap, flex_wrap, css_wrap_type_t)
- (void)setBackgroundColor:(UIColor *)color
{
_backgroundColor = color;
_isBGColorExplicitlySet = YES;
[self dirtyPropagation];
}

Просмотреть файл

@ -10,13 +10,12 @@
#import "RCTView.h"
#import "RCTAutoInsetsProtocol.h"
#import "RCTBorderDrawing.h"
#import "RCTConvert.h"
#import "RCTLog.h"
#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]) {
@ -31,10 +30,6 @@ static UIView *RCTViewHitTest(UIView *view, CGPoint point, UIEvent *event)
return nil;
}
static BOOL RCTEllipseGetIntersectionsWithLine(CGRect ellipseBoundingRect, CGPoint p1, CGPoint p2, CGPoint intersections[2]);
static CGPathRef RCTPathCreateWithRoundedRect(CGRect rect, CGFloat topLeftRadiusX, CGFloat topLeftRadiusY, CGFloat topRightRadiusX, CGFloat topRightRadiusY, CGFloat bottomLeftRadiusX, CGFloat bottomLeftRadiusY, CGFloat bottomRightRadiusX, CGFloat bottomRightRadiusY, const CGAffineTransform *transform);
static void RCTPathAddEllipticArc(CGMutablePathRef path, const CGAffineTransform *m, CGFloat x, CGFloat y, CGFloat xRadius, CGFloat yRadius, CGFloat startAngle, CGFloat endAngle, bool clockwise);
@implementation UIView (RCTViewUnmounting)
- (void)react_remountAllSubviews
@ -443,246 +438,88 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
[self.layer setNeedsDisplay];
}
- (UIImage *)borderImage:(out CGRect *)contentsCenter
- (UIEdgeInsets)bordersAsInsets
{
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 bottomRightRadius = MIN(_borderBottomRightRadius >= 0 ? _borderBottomRightRadius : radius, maxRadius);
const CGFloat borderWidth = MAX(0, _borderWidth);
const CGFloat topWidth = _borderTopWidth >= 0 ? _borderTopWidth : borderWidth;
const CGFloat rightWidth = _borderRightWidth >= 0 ? _borderRightWidth : borderWidth;
const CGFloat bottomWidth = _borderBottomWidth >= 0 ? _borderBottomWidth : borderWidth;
const CGFloat leftWidth = _borderLeftWidth >= 0 ? _borderLeftWidth : borderWidth;
const BOOL hasCornerRadii =
topLeftRadius > RCTViewBorderThreshold ||
topRightRadius > RCTViewBorderThreshold ||
bottomLeftRadius > RCTViewBorderThreshold ||
bottomRightRadius > RCTViewBorderThreshold;
return (UIEdgeInsets) {
_borderTopWidth >= 0 ? _borderTopWidth : borderWidth,
_borderLeftWidth >= 0 ? _borderLeftWidth : borderWidth,
_borderBottomWidth >= 0 ? _borderBottomWidth : borderWidth,
_borderRightWidth >= 0 ? _borderRightWidth : borderWidth,
};
}
const BOOL hasBorders =
topWidth > RCTViewBorderThreshold ||
rightWidth > RCTViewBorderThreshold ||
bottomWidth > RCTViewBorderThreshold ||
leftWidth > RCTViewBorderThreshold;
- (RCTCornerRadii)cornerRadii
{
const CGRect bounds = self.bounds;
const CGFloat maxRadius = MIN(bounds.size.height, bounds.size.width);
const CGFloat radius = MAX(0, _borderRadius);
if (!hasCornerRadii && !hasBorders) {
return nil;
}
return (RCTCornerRadii){
MIN(_borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius, maxRadius),
MIN(_borderTopRightRadius >= 0 ? _borderTopRightRadius : radius, maxRadius),
MIN(_borderBottomLeftRadius >= 0 ? _borderBottomLeftRadius : radius, maxRadius),
MIN(_borderBottomRightRadius >= 0 ? _borderBottomRightRadius : radius, maxRadius),
};
}
const CGFloat innerTopLeftRadiusX = MAX(0, topLeftRadius - leftWidth);
const CGFloat innerTopLeftRadiusY = MAX(0, topLeftRadius - topWidth);
const CGFloat innerTopRightRadiusX = MAX(0, topRightRadius - rightWidth);
const CGFloat innerTopRightRadiusY = MAX(0, topRightRadius - topWidth);
const CGFloat innerBottomLeftRadiusX = MAX(0, bottomLeftRadius - leftWidth);
const CGFloat innerBottomLeftRadiusY = MAX(0, bottomLeftRadius - bottomWidth);
const CGFloat innerBottomRightRadiusX = MAX(0, bottomRightRadius - rightWidth);
const CGFloat innerBottomRightRadiusY = MAX(0, bottomRightRadius - bottomWidth);
const UIEdgeInsets edgeInsets = UIEdgeInsetsMake(topWidth + MAX(innerTopLeftRadiusY, innerTopRightRadiusY), leftWidth + MAX(innerTopLeftRadiusX, innerBottomLeftRadiusX), bottomWidth + MAX(innerBottomLeftRadiusY, innerBottomRightRadiusY), rightWidth + + MAX(innerBottomRightRadiusX, innerTopRightRadiusX));
const CGSize size = CGSizeMake(edgeInsets.left + 1 + edgeInsets.right, edgeInsets.top + 1 + edgeInsets.bottom);
const CGFloat alpha = CGColorGetAlpha(_backgroundColor.CGColor);
const BOOL opaque = (self.clipsToBounds || !hasCornerRadii) && alpha == 1.0;
UIGraphicsBeginImageContextWithOptions(size, opaque, 0.0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
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);
CGContextSetFillColorWithColor(ctx, _backgroundColor.CGColor);
CGContextAddPath(ctx, path);
CGContextFillPath(ctx);
CGContextRestoreGState(ctx);
}
CGContextAddPath(ctx, path);
CGPathRelease(path);
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;
if ((hasClipping || !hasRadius) && hasEqualColor) {
CGContextSetFillColorWithColor(ctx, _borderColor);
CGContextAddRect(ctx, rect);
CGContextAddPath(ctx, insetPath);
CGContextEOFillPath(ctx);
} else {
BOOL didSet = NO;
CGPoint topLeft;
if (innerTopLeftRadiusX > 0 && innerTopLeftRadiusY > 0) {
CGPoint points[2];
RCTEllipseGetIntersectionsWithLine(CGRectMake(leftWidth, topWidth, 2 * innerTopLeftRadiusX, 2 * innerTopLeftRadiusY), CGPointMake(0, 0), CGPointMake(leftWidth, topWidth), points);
if (!isnan(points[1].x) && !isnan(points[1].y)) {
topLeft = points[1];
didSet = YES;
}
}
if (!didSet) {
topLeft = CGPointMake(leftWidth, topWidth);
}
didSet = NO;
CGPoint bottomLeft;
if (innerBottomLeftRadiusX > 0 && innerBottomLeftRadiusY > 0) {
CGPoint points[2];
RCTEllipseGetIntersectionsWithLine(CGRectMake(leftWidth, (size.height - bottomWidth) - 2 * innerBottomLeftRadiusY, 2 * innerBottomLeftRadiusX, 2 * innerBottomLeftRadiusY), CGPointMake(0, size.height), CGPointMake(leftWidth, size.height - bottomWidth), points);
if (!isnan(points[1].x) && !isnan(points[1].y)) {
bottomLeft = points[1];
didSet = YES;
}
}
if (!didSet) {
bottomLeft = CGPointMake(leftWidth, size.height - bottomWidth);
}
didSet = NO;
CGPoint topRight;
if (innerTopRightRadiusX > 0 && innerTopRightRadiusY > 0) {
CGPoint points[2];
RCTEllipseGetIntersectionsWithLine(CGRectMake((size.width - rightWidth) - 2 * innerTopRightRadiusX, topWidth, 2 * innerTopRightRadiusX, 2 * innerTopRightRadiusY), CGPointMake(size.width, 0), CGPointMake(size.width - rightWidth, topWidth), points);
if (!isnan(points[0].x) && !isnan(points[0].y)) {
topRight = points[0];
didSet = YES;
}
}
if (!didSet) {
topRight = CGPointMake(size.width - rightWidth, topWidth);
}
didSet = NO;
CGPoint bottomRight;
if (innerBottomRightRadiusX > 0 && innerBottomRightRadiusY > 0) {
CGPoint points[2];
RCTEllipseGetIntersectionsWithLine(CGRectMake((size.width - rightWidth) - 2 * innerBottomRightRadiusX, (size.height - bottomWidth) - 2 * innerBottomRightRadiusY, 2 * innerBottomRightRadiusX, 2 * innerBottomRightRadiusY), CGPointMake(size.width, size.height), CGPointMake(size.width - rightWidth, size.height - bottomWidth), points);
if (!isnan(points[0].x) && !isnan(points[0].y)) {
bottomRight = points[0];
didSet = YES;
}
}
if (!didSet) {
bottomRight = CGPointMake(size.width - rightWidth, size.height - bottomWidth);
}
// RIGHT
if (rightWidth > 0) {
CGContextSaveGState(ctx);
const CGPoint points[] = {
CGPointMake(size.width, 0),
topRight,
bottomRight,
CGPointMake(size.width, size.height),
};
CGContextSetFillColorWithColor(ctx, _borderRightColor ?: _borderColor);
CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
CGContextFillPath(ctx);
CGContextRestoreGState(ctx);
}
// BOTTOM
if (bottomWidth > 0) {
CGContextSaveGState(ctx);
const CGPoint points[] = {
CGPointMake(0, size.height),
bottomLeft,
bottomRight,
CGPointMake(size.width, size.height),
};
CGContextSetFillColorWithColor(ctx, _borderBottomColor ?: _borderColor);
CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
CGContextFillPath(ctx);
CGContextRestoreGState(ctx);
}
// LEFT
if (leftWidth > 0) {
CGContextSaveGState(ctx);
const CGPoint points[] = {
CGPointMake(0, 0),
topLeft,
bottomLeft,
CGPointMake(0, size.height),
};
CGContextSetFillColorWithColor(ctx, _borderLeftColor ?: _borderColor);
CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
CGContextFillPath(ctx);
CGContextRestoreGState(ctx);
}
// TOP
if (topWidth > 0) {
CGContextSaveGState(ctx);
const CGPoint points[] = {
CGPointMake(0, 0),
topLeft,
topRight,
CGPointMake(size.width, 0),
};
CGContextSetFillColorWithColor(ctx, _borderTopColor ?: _borderColor);
CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
CGContextFillPath(ctx);
CGContextRestoreGState(ctx);
}
}
CGPathRelease(insetPath);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
*contentsCenter = CGRectMake(edgeInsets.left / size.width, edgeInsets.top / size.height, 1.0 / size.width, 1.0 / size.height);
return [image resizableImageWithCapInsets:edgeInsets];
- (RCTBorderColors)borderColors
{
return (RCTBorderColors){
_borderTopColor ?: _borderColor,
_borderLeftColor ?: _borderColor,
_borderBottomColor ?: _borderColor,
_borderRightColor ?: _borderColor
};
}
- (void)displayLayer:(CALayer *)layer
{
CGRect contentsCenter = {.size = {1, 1}};
UIImage *image = [self borderImage:&contentsCenter];
const RCTCornerRadii cornerRadii = [self cornerRadii];
const UIEdgeInsets borderInsets = [self bordersAsInsets];
const RCTBorderColors borderColors = [self borderColors];
if (image && RCTRunningInTestEnvironment()) {
BOOL useIOSBorderRendering =
!RCTRunningInTestEnvironment() &&
RCTCornerRadiiAreEqual(cornerRadii) &&
RCTBorderInsetsAreEqual(borderInsets) &&
RCTBorderColorsAreEqual(borderColors);
// TODO: A problem with this is that iOS draws borders in front of the content
// whereas CSS draws them behind the content. Also iOS clips to the outside of
// the border, but CSS clips to the inside. To solve this, we'll need to add
// a container view inside the main view to correctly clip the subviews.
if (useIOSBorderRendering) {
layer.cornerRadius = cornerRadii.topLeft;
layer.borderColor = borderColors.left;
layer.borderWidth = borderInsets.left;
layer.backgroundColor = _backgroundColor.CGColor;
layer.contents = nil;
layer.needsDisplayOnBoundsChange = NO;
layer.mask = nil;
return;
}
UIImage *image = RCTGetBorderImage([self cornerRadii],
[self bordersAsInsets],
[self borderColors],
_backgroundColor.CGColor,
self.clipsToBounds);
const CGRect contentsCenter = ({
CGSize size = image.size;
UIEdgeInsets insets = image.capInsets;
CGRectMake(
insets.left / size.width,
insets.top / size.height,
1.0 / size.width,
1.0 / size.height
);
});
if (RCTRunningInTestEnvironment()) {
const CGSize size = self.bounds.size;
UIGraphicsBeginImageContextWithOptions(size, NO, image.scale);
[image drawInRect:(CGRect){CGPointZero, size}];
@ -690,12 +527,12 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
UIGraphicsEndImageContext();
}
layer.backgroundColor = [image ? [UIColor clearColor] : _backgroundColor CGColor];
layer.backgroundColor = NULL;
layer.contents = (id)image.CGImage;
layer.contentsCenter = contentsCenter;
layer.contentsScale = image.scale ?: 1.0;
layer.contentsScale = image.scale;
layer.magnificationFilter = kCAFilterNearest;
layer.needsDisplayOnBoundsChange = image != nil;
layer.needsDisplayOnBoundsChange = YES;
[self updateClippingForLayer:layer];
}
@ -706,30 +543,19 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
CGFloat cornerRadius = 0;
if (self.clipsToBounds) {
if (_borderRadius > 0 && _borderTopLeftRadius < 0 && _borderTopRightRadius < 0 && _borderBottomLeftRadius < 0 && _borderBottomRightRadius < 0) {
cornerRadius = _borderRadius;
const RCTCornerRadii cornerRadii = [self cornerRadii];
if (RCTCornerRadiiAreEqual(cornerRadii)) {
cornerRadius = cornerRadii.topLeft;
} 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;
}
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
CGPathRef path = RCTPathCreateWithRoundedRect(self.bounds, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL);
shapeLayer.path = path;
CGPathRelease(path);
mask = shapeLayer;
}
}
@ -790,74 +616,3 @@ setBorderRadius(BottomLeft)
setBorderRadius(BottomRight)
@end
static void RCTPathAddEllipticArc(CGMutablePathRef path, const CGAffineTransform *m, CGFloat x, CGFloat y, CGFloat xRadius, CGFloat yRadius, CGFloat startAngle, CGFloat endAngle, bool clockwise)
{
CGFloat xScale = 1, yScale = 1, radius = 0;
if (xRadius != 0) {
xScale = 1;
yScale = yRadius / xRadius;
radius = xRadius;
} else if (yRadius != 0) {
xScale = xRadius / yRadius;
yScale = 1;
radius = yRadius;
}
CGAffineTransform t = CGAffineTransformMakeTranslation(x, y);
t = CGAffineTransformScale(t, xScale, yScale);
if (m != NULL) {
t = CGAffineTransformConcat(t, *m);
}
CGPathAddArc(path, &t, 0, 0, radius, startAngle, endAngle, clockwise);
}
static CGPathRef RCTPathCreateWithRoundedRect(CGRect rect, CGFloat topLeftRadiusX, CGFloat topLeftRadiusY, CGFloat topRightRadiusX, CGFloat topRightRadiusY, CGFloat bottomLeftRadiusX, CGFloat bottomLeftRadiusY, CGFloat bottomRightRadiusX, CGFloat bottomRightRadiusY, const CGAffineTransform *transform)
{
const CGFloat minX = CGRectGetMinX(rect);
const CGFloat minY = CGRectGetMinY(rect);
const CGFloat maxX = CGRectGetMaxX(rect);
const CGFloat maxY = CGRectGetMaxY(rect);
CGMutablePathRef path = CGPathCreateMutable();
RCTPathAddEllipticArc(path, transform, minX + topLeftRadiusX, minY + topLeftRadiusY, topLeftRadiusX, topLeftRadiusY, M_PI, 3 * M_PI_2, false);
RCTPathAddEllipticArc(path, transform, maxX - topRightRadiusX, minY + topRightRadiusY, topRightRadiusX, topRightRadiusY, 3 * M_PI_2, 0, false);
RCTPathAddEllipticArc(path, transform, maxX - bottomRightRadiusX, maxY - bottomRightRadiusY, bottomRightRadiusX, bottomRightRadiusY, 0, M_PI_2, false);
RCTPathAddEllipticArc(path, transform, minX + bottomLeftRadiusX, maxY - bottomLeftRadiusY, bottomLeftRadiusX, bottomLeftRadiusY, M_PI_2, M_PI, false);
CGPathCloseSubpath(path);
return path;
}
static BOOL RCTEllipseGetIntersectionsWithLine(CGRect ellipseBoundingRect, CGPoint p1, CGPoint p2, CGPoint intersections[2])
{
const CGFloat ellipseCenterX = CGRectGetMidX(ellipseBoundingRect);
const CGFloat ellipseCenterY = CGRectGetMidY(ellipseBoundingRect);
// ellipseBoundingRect.origin.x -= ellipseCenterX;
// ellipseBoundingRect.origin.y -= ellipseCenterY;
p1.x -= ellipseCenterX;
p1.y -= ellipseCenterY;
p2.x -= ellipseCenterX;
p2.y -= ellipseCenterY;
const CGFloat m = (p2.y - p1.y) / (p2.x - p1.x);
const CGFloat a = ellipseBoundingRect.size.width / 2;
const CGFloat b = ellipseBoundingRect.size.height / 2;
const CGFloat c = p1.y - m * p1.x;
const CGFloat A = (b * b + a * a * m * m);
const CGFloat B = 2 * a * a * c * m;
const CGFloat D = sqrt((a * a * (b * b - c * c)) / A + pow(B / (2 * A), 2));
const CGFloat x_ = -B / (2 * A);
const CGFloat x1 = x_ + D;
const CGFloat x2 = x_ - D;
const CGFloat y1 = m * x1 + c;
const CGFloat y2 = m * x2 + c;
intersections[0] = CGPointMake(x1 + ellipseCenterX, y1 + ellipseCenterY);
intersections[1] = CGPointMake(x2 + ellipseCenterX, y2 + ellipseCenterY);
return YES;
}

Просмотреть файл

@ -102,10 +102,7 @@ RCT_REMAP_VIEW_PROPERTY(shadowOffset, layer.shadowOffset, CGSize);
RCT_REMAP_VIEW_PROPERTY(shadowOpacity, layer.shadowOpacity, float)
RCT_REMAP_VIEW_PROPERTY(shadowRadius, layer.shadowRadius, CGFloat)
RCT_REMAP_VIEW_PROPERTY(transformMatrix, layer.transform, CATransform3D)
RCT_CUSTOM_VIEW_PROPERTY(overflow, css_overflow, RCTView)
{
view.clipsToBounds = json ? ![RCTConvert css_overflow:json] : defaultView.clipsToBounds;
}
RCT_REMAP_VIEW_PROPERTY(overflow, clipsToBounds, css_clip_t)
RCT_CUSTOM_VIEW_PROPERTY(pointerEvents, RCTPointerEvents, RCTView)
{
if ([view respondsToSelector:@selector(setPointerEvents:)]) {
@ -260,7 +257,7 @@ RCT_EXPORT_SHADOW_PROPERTY(paddingBottom, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(paddingLeft, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(paddingVertical, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(paddingHorizontal, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(padding, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(padding, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(flex, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(flexDirection, css_flex_direction_t)

Просмотреть файл

@ -21,6 +21,11 @@
*/
- (void)reactSetFrame:(CGRect)frame;
/**
* Used to improve performance when compositing views with translucent content.
*/
- (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor;
/**
* This method finds and returns the containing view controller for the view.
*/

Просмотреть файл

@ -83,6 +83,11 @@
self.layer.bounds = bounds;
}
- (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor
{
self.backgroundColor = inheritedBackgroundColor;
}
- (UIViewController *)backingViewController
{
id responder = [self nextResponder];

Просмотреть файл

@ -2,6 +2,7 @@
"name": "react-native",
"version": "0.4.4",
"description": "A framework for building native apps using React",
"license": "BSD-3-Clause",
"repository": {
"type": "git",
"url": "git@github.com:facebook/react-native.git"