Merge pull request #1446 from tadeuzagallo/Update_Fri_29_May
Update fri 29 may
16
.travis.yml
@ -1,18 +1,28 @@
|
||||
language: objective-c
|
||||
|
||||
osx_image: beta-xcode6.3
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
directories:
|
||||
- node_modules
|
||||
- .nvm
|
||||
|
||||
before_install:
|
||||
- brew update
|
||||
install:
|
||||
- brew reinstall flow xctool
|
||||
- brew reinstall flow xctool nvm
|
||||
- mkdir -p .nvm
|
||||
- cp $(brew --prefix nvm)/nvm-exec .nvm/
|
||||
- export NVM_DIR=.nvm
|
||||
- source $(brew --prefix nvm)/nvm.sh
|
||||
- nvm install v0.10
|
||||
- npm config set spin=false
|
||||
- npm install
|
||||
|
||||
script:
|
||||
- |
|
||||
nvm use v0.10
|
||||
|
||||
if [ "$TEST_TYPE" = objc ]
|
||||
then
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 270 KiB After Width: | Height: | Size: 269 KiB |
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 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
|
||||
|
@ -1,54 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.autoMockOff();
|
||||
|
||||
var AnimationUtils = require('AnimationUtils');
|
||||
|
||||
describe('AnimationUtils', function() {
|
||||
var DURATION = 300;
|
||||
|
||||
var Samples = {
|
||||
easeInQuad: [0,0.0030864197530864196,0.012345679012345678,0.027777777777777776,0.04938271604938271,0.0771604938271605,0.1111111111111111,0.15123456790123457,0.19753086419753085,0.25,0.308641975308642,0.37345679012345684,0.4444444444444444,0.5216049382716049,0.6049382716049383,0.6944444444444445,0.7901234567901234,0.8919753086419753,1],
|
||||
easeOutQuad: [0,0.10802469135802469,0.20987654320987653,0.3055555555555555,0.3950617283950617,0.47839506172839513,0.5555555555555556,0.6265432098765432,0.691358024691358,0.75,0.8024691358024691,0.8487654320987654,0.888888888888889,0.9228395061728394,0.9506172839506174,0.9722222222222221,0.9876543209876543,0.9969135802469136,1],
|
||||
easeInOutQuad: [0,0.006172839506172839,0.024691358024691357,0.05555555555555555,0.09876543209876543,0.154320987654321,0.2222222222222222,0.30246913580246915,0.3950617283950617,0.5,0.6049382716049383,0.697530864197531,0.7777777777777777,0.845679012345679,0.9012345679012346,0.9444444444444444,0.9753086419753086,0.9938271604938271,1],
|
||||
easeInCubic: [0,0.00017146776406035664,0.0013717421124828531,0.004629629629629629,0.010973936899862825,0.021433470507544586,0.037037037037037035,0.05881344307270234,0.0877914951989026,0.125,0.1714677640603567,0.22822359396433475,0.2962962962962963,0.37671467764060357,0.4705075445816187,0.5787037037037038,0.7023319615912208,0.8424211248285322,1],
|
||||
easeOutCubic: [0,0.15757887517146785,0.2976680384087792,0.42129629629629617,0.5294924554183813,0.6232853223593964,0.7037037037037036,0.7717764060356652,0.8285322359396433,0.875,0.9122085048010974,0.9411865569272977,0.9629629629629629,0.9785665294924554,0.9890260631001372,0.9953703703703703,0.9986282578875172,0.9998285322359396,1],
|
||||
easeInOutCubic: [0,0.0006858710562414266,0.0054869684499314125,0.018518518518518517,0.0438957475994513,0.08573388203017834,0.14814814814814814,0.23525377229080935,0.3511659807956104,0.5,0.6488340192043895,0.7647462277091908,0.8518518518518519,0.9142661179698217,0.9561042524005487,0.9814814814814815,0.9945130315500685,0.9993141289437586,1],
|
||||
easeInQuart: [0,0.000009525986892242035,0.00015241579027587256,0.0007716049382716049,0.002438652644413961,0.005953741807651274,0.012345679012345678,0.02287189452827313,0.039018442310623375,0.0625,0.09525986892242039,0.1394699740893157,0.19753086419753085,0.2720717116293248,0.3659503124523701,0.48225308641975323,0.624295076969974,0.7956199512269471,1],
|
||||
easeOutQuart: [0,0.20438004877305294,0.375704923030026,0.5177469135802468,0.6340496875476299,0.7279282883706752,0.802469135802469,0.8605300259106843,0.9047401310775796,0.9375,0.9609815576893767,0.9771281054717269,0.9876543209876543,0.9940462581923487,0.997561347355586,0.9992283950617284,0.9998475842097241,0.9999904740131078,1],
|
||||
easeInOutQuart: [0,0.00007620789513793628,0.0012193263222069805,0.006172839506172839,0.019509221155311687,0.047629934461210194,0.09876543209876543,0.18297515622618504,0.312147538484987,0.5,0.687852461515013,0.8170248437738151,0.9012345679012346,0.9523700655387898,0.9804907788446883,0.9938271604938271,0.998780673677793,0.999923792104862,1],
|
||||
easeInQuint: [0,5.292214940134463e-7,0.000016935087808430282,0.00012860082304526747,0.000541922809869769,0.0016538171687920206,0.004115226337448559,0.008894625649883995,0.01734152991583261,0.03125,0.05292214940134466,0.08523165083235959,0.1316872427983539,0.1964962361767346,0.28462802079628785,0.401877572016461,0.5549289573066435,0.75141884282545,1],
|
||||
easeOutQuint: [0,0.24858115717454998,0.4450710426933565,0.598122427983539,0.7153719792037121,0.8035037638232654,0.868312757201646,0.9147683491676404,0.9470778505986553,0.96875,0.9826584700841674,0.991105374350116,0.9958847736625515,0.998346182831208,0.9994580771901302,0.9998713991769548,0.9999830649121916,0.999999470778506,1],
|
||||
easeInOutQuint: [0,0.000008467543904215141,0.0002709614049348845,0.0020576131687242796,0.008670764957916305,0.02646107470067233,0.06584362139917695,0.14231401039814393,0.27746447865332174,0.5,0.7225355213466782,0.8576859896018563,0.934156378600823,0.9735389252993276,0.9913292350420837,0.9979423868312757,0.9997290385950651,0.9999915324560957,1],
|
||||
easeInSine: [0,0.003805301908254455,0.01519224698779198,0.03407417371093169,0.06030737921409157,0.09369221296335006,0.1339745962155613,0.1808479557110082,0.233955556881022,0.2928932188134524,0.35721239031346064,0.42642356364895384,0.4999999999999999,0.5773817382593005,0.6579798566743311,0.7411809548974793,0.8263518223330696,0.9128442572523416,0.9999999999999999],
|
||||
easeOutSine: [0,0.08715574274765817,0.17364817766693033,0.25881904510252074,0.3420201433256687,0.42261826174069944,0.49999999999999994,0.573576436351046,0.6427876096865393,0.7071067811865475,0.766044443118978,0.8191520442889918,0.8660254037844386,0.9063077870366499,0.9396926207859083,0.9659258262890683,0.984807753012208,0.9961946980917455,1],
|
||||
easeInOutSine: [0,0.00759612349389599,0.030153689607045786,0.06698729810778065,0.116977778440511,0.17860619515673032,0.24999999999999994,0.32898992833716556,0.4131759111665348,0.49999999999999994,0.5868240888334652,0.6710100716628343,0.7499999999999999,0.8213938048432696,0.883022221559489,0.9330127018922194,0.9698463103929542,0.9924038765061041,1],
|
||||
easeInExpo: [0,0.0014352875901128893,0.002109491677524035,0.0031003926796253885,0.004556754060844206,0.006697218616039631,0.009843133202303688,0.014466792379488908,0.021262343752724643,0.03125,0.045929202883612456,0.06750373368076916,0.09921256574801243,0.1458161299470146,0.2143109957132682,0.31498026247371835,0.46293735614364506,0.6803950000871883,1],
|
||||
easeOutExpo: [0,0.31960499991281155,0.5370626438563548,0.6850197375262816,0.7856890042867318,0.8541838700529854,0.9007874342519875,0.9324962663192309,0.9540707971163875,0.96875,0.9787376562472754,0.9855332076205111,0.9901568667976963,0.9933027813839603,0.9954432459391558,0.9968996073203746,0.9978905083224759,0.9985647124098871,1],
|
||||
easeInOutExpo: [0,0.0010547458387620175,0.002278377030422103,0.004921566601151844,0.010631171876362321,0.022964601441806228,0.049606282874006216,0.1071554978566341,0.23146867807182253,0.5,0.7685313219281775,0.892844502143366,0.9503937171259937,0.9770353985581938,0.9893688281236377,0.9950784333988482,0.9977216229695779,0.998945254161238,1],
|
||||
easeInCirc: [0,0.0015444024660317135,0.006192010000093506,0.013986702816730645,0.025003956956430873,0.03935464078941209,0.057190958417936644,0.07871533601238889,0.10419358352238339,0.1339745962155614,0.1685205807169019,0.20845517506805522,0.2546440075000701,0.3083389112228482,0.37146063894529113,0.4472292016074334,0.5418771527091488,0.6713289009389102,1],
|
||||
easeOutCirc: [0,0.3286710990610898,0.45812284729085123,0.5527707983925666,0.6285393610547089,0.6916610887771518,0.7453559924999298,0.7915448249319448,0.8314794192830981,0.8660254037844386,0.8958064164776166,0.9212846639876111,0.9428090415820634,0.9606453592105879,0.9749960430435691,0.9860132971832694,0.9938079899999065,0.9984555975339683,1],
|
||||
easeInOutCirc: [0,0.003096005000046753,0.012501978478215436,0.028595479208968322,0.052096791761191696,0.08426029035845095,0.12732200375003505,0.18573031947264557,0.2709385763545744,0.5,0.7290614236454256,0.8142696805273546,0.8726779962499649,0.915739709641549,0.9479032082388084,0.9714045207910317,0.9874980215217846,0.9969039949999532,1],
|
||||
easeInElastic: [0,0.0008570943160003016,0.0020526300563455885,0.0005383775388688477,-0.003807112477441741,-0.005595444524068916,0.0017092421431128787,0.014076838118604966,0.012696991251677569,-0.015625000000000045,-0.045618646044515744,-0.01936028903971309,0.07600123467884114,0.13030605320629246,-0.012461076179381799,-0.29598462833976175,-0.3176868895106366,0.2694906924487451,1],
|
||||
easeOutElastic: [0,0.7305093075512543,1.3176868895106364,1.2959846283397618,1.0124610761793817,0.8696939467937076,0.9239987653211588,1.019360289039713,1.0456186460445158,1.015625,0.9873030087483224,0.9859231618813951,0.9982907578568871,1.005595444524069,1.0038071124774417,0.9994616224611311,0.9979473699436544,0.9991429056839997,1],
|
||||
easeInOutElastic: [0,0.0010420781824747765,-0.0003083357248478688,-0.004888288728445655,0.0010292130059457788,0.022895545534212507,-0.0028843488305936938,-0.10707491183281304,0.004488485931276091,0.5,0.995511514068724,1.107074911832813,1.0028843488305939,0.9771044544657875,0.9989707869940542,1.0048882887284456,1.000308335724848,0.9989579218175252,1],
|
||||
easeInBack: [0,-0.004788556241426612,-0.017301289437585736,-0.0347587962962963,-0.05438167352537723,-0.07339051783264748,-0.08900592592592595,-0.09844849451303156,-0.0989388203017833,-0.08769750000000004,-0.06194513031550073,-0.018902307956104283,0.044210370370370254,0.13017230795610413,0.2417629080932785,0.3817615740740742,0.5529477091906719,0.7581007167352535,0.9999999999999998],
|
||||
easeOutBack: [2.220446049250313e-16,0.24189928326474652,0.44705229080932807,0.6182384259259258,0.7582370919067215,0.8698276920438959,0.9557896296296297,1.0189023079561044,1.0619451303155008,1.0876975,1.0989388203017834,1.0984484945130315,1.089005925925926,1.0733905178326475,1.0543816735253773,1.0347587962962963,1.0173012894375857,1.0047885562414267,1],
|
||||
easeInOutBack: [0,-0.01355231550068587,-0.04434668449931412,-0.07758924074074074,-0.09848611796982167,-0.0922434499314129,-0.0440673703703704,0.060835986968449905,0.237260488340192,0.5,0.762739511659808,0.9391640130315503,1.0440673703703702,1.0922434499314129,1.0984861179698218,1.0775892407407408,1.0443466844993141,1.0135523155006858,1],
|
||||
};
|
||||
|
||||
Object.keys(Samples).forEach(function(type) {
|
||||
it('should interpolate ' + type, function() {
|
||||
expect(AnimationUtils.evaluateEasingFunction(DURATION, type))
|
||||
.toEqual(Samples[type]);
|
||||
});
|
||||
});
|
||||
});
|
@ -407,15 +407,23 @@ var TextInput = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
getChildContext: function(): Object {
|
||||
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(): Object {
|
||||
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 */,
|
||||
|
61
React/Views/RCTBorderDrawing.h
Normal file
@ -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);
|
331
React/Views/RCTBorderDrawing.m
Normal file
@ -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"
|
||||
|
@ -25,5 +25,5 @@ trap cleanup EXIT
|
||||
|
||||
xctool \
|
||||
-project Examples/UIExplorer/UIExplorer.xcodeproj \
|
||||
-scheme UIExplorer -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 5,OS=8.1' \
|
||||
-scheme UIExplorer -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 5,OS=8.3' \
|
||||
test
|
||||
|