Updates from Fri Feb 13
- [ReactNative] Fix throttle warning and warn in callback instead of render | Christopher Chedeau - [react-packager][streamline oss] Remove react-page-middleware | Amjad Masad - [ReactNative] Turn on perf measurement around a group feed load | Jing Chen - Implemented Layout animations | Nick Lockwood - [ReactNative] Revert D1815137 - avoid dropping touch start on missing target | Eric Vicenti - Moved RKPOPAnimationManager into FBReactKitComponents | Nick Lockwood - Extracted RKAnimationManager | Nick Lockwood
This commit is contained in:
parent
472c287cd3
commit
ef842c285b
|
@ -0,0 +1,250 @@
|
|||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule ListViewPagingExample
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
Image,
|
||||
LayoutAnimation,
|
||||
ListView,
|
||||
ListViewDataSource,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
var PAGE_SIZE = 4;
|
||||
var THUMB_URLS = ['https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851549_767334479959628_274486868_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851561_767334496626293_1958532586_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851579_767334503292959_179092627_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851589_767334513292958_1747022277_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851563_767334559959620_1193692107_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851593_767334566626286_1953955109_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851591_767334523292957_797560749_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851567_767334529959623_843148472_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851548_767334489959627_794462220_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851575_767334539959622_441598241_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851573_767334549959621_534583464_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851583_767334573292952_1519550680_n.png'];
|
||||
var NUM_SECTIONS = 100;
|
||||
var NUM_ROWS_PER_SECTION = 10;
|
||||
|
||||
var Thumb = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {thumbIndex: this._getThumbIdx(), dir: 'row'};
|
||||
},
|
||||
_getThumbIdx: function() {
|
||||
return Math.floor(Math.random() * THUMB_URLS.length);
|
||||
},
|
||||
_onPressThumb: function() {
|
||||
var config = layoutAnimationConfigs[this.state.thumbIndex % layoutAnimationConfigs.length];
|
||||
LayoutAnimation.configureNext(config);
|
||||
this.setState({
|
||||
thumbIndex: this._getThumbIdx(),
|
||||
dir: this.state.dir === 'row' ? 'column' : 'row',
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<TouchableOpacity onPress={this._onPressThumb}>
|
||||
<View style={[styles.buttonContents, {flexDirection: this.state.dir}]}>
|
||||
<Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
|
||||
<Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
|
||||
<Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
|
||||
{this.state.dir === 'column' ?
|
||||
<Text>
|
||||
Oooo, look at this new text! So awesome it may just be crazy.
|
||||
Let me keep typing here so it wraps at least one line.
|
||||
</Text> :
|
||||
<Text />
|
||||
}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var ListViewPagingExample = React.createClass({
|
||||
statics: {
|
||||
title: '<ListView> - Paging',
|
||||
description: 'Floating headers & layout animations.'
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
var getSectionData = (dataBlob, sectionID) => {
|
||||
return dataBlob[sectionID];
|
||||
};
|
||||
var getRowData = (dataBlob, sectionID, rowID) => {
|
||||
return dataBlob[rowID];
|
||||
};
|
||||
|
||||
var dataSource = new ListViewDataSource({
|
||||
getRowData: getRowData,
|
||||
getSectionHeaderData: getSectionData,
|
||||
rowHasChanged: (row1, row2) => row1 !== row2,
|
||||
sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
|
||||
});
|
||||
|
||||
var dataBlob = {};
|
||||
var sectionIDs = [];
|
||||
var rowIDs = [];
|
||||
for (var ii = 0; ii < NUM_SECTIONS; ii++) {
|
||||
var sectionName = 'Section ' + ii;
|
||||
sectionIDs.push(sectionName);
|
||||
dataBlob[sectionName] = sectionName;
|
||||
rowIDs[ii] = [];
|
||||
|
||||
for (var jj = 0; jj < NUM_ROWS_PER_SECTION; jj++) {
|
||||
var rowName = 'S' + ii + ', R' + jj;
|
||||
rowIDs[ii].push(rowName);
|
||||
dataBlob[rowName] = rowName;
|
||||
}
|
||||
}
|
||||
return {
|
||||
dataSource: dataSource.cloneWithRowsAndSections(dataBlob, sectionIDs, rowIDs),
|
||||
headerPressCount: 0,
|
||||
};
|
||||
},
|
||||
|
||||
renderRow: function(rowData, sectionID, rowID) {
|
||||
return (<Thumb text={rowData}/>);
|
||||
},
|
||||
|
||||
renderSectionHeader: function(sectionData, sectionID) {
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.text}>
|
||||
{sectionData}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
renderHeader: function() {
|
||||
var headerLikeText = this.state.headerPressCount % 2 ?
|
||||
<View><Text style={styles.text}>1 Like</Text></View> :
|
||||
null;
|
||||
return (
|
||||
<TouchableOpacity onPress={this._onPressHeader}>
|
||||
<View style={styles.header}>
|
||||
{headerLikeText}
|
||||
<View>
|
||||
<Text style={styles.text}>
|
||||
Table Header (click me)
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
|
||||
renderFooter: function() {
|
||||
return (
|
||||
<View style={styles.header}>
|
||||
<Text onPress={() => console.log('Footer!')} style={styles.text}>
|
||||
Table Footer
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<ListView
|
||||
style={styles.listview}
|
||||
dataSource={this.state.dataSource}
|
||||
onChangeVisibleRows={(visibleRows, changedRows) => console.log({visibleRows, changedRows})}
|
||||
renderHeader={this.renderHeader}
|
||||
renderFooter={this.renderFooter}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
renderRow={this.renderRow}
|
||||
initialListSize={10}
|
||||
pageSize={PAGE_SIZE}
|
||||
scrollRenderAheadDistance={2000}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
_onPressHeader: function() {
|
||||
var config = layoutAnimationConfigs[Math.floor(this.state.headerPressCount / 2) % layoutAnimationConfigs.length];
|
||||
LayoutAnimation.configureNext(config);
|
||||
this.setState({headerPressCount: this.state.headerPressCount + 1});
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
listview: {
|
||||
backgroundColor: '#B0C4DE',
|
||||
},
|
||||
header: {
|
||||
height: 40,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#3B5998',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
text: {
|
||||
color: 'white',
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
rowText: {
|
||||
color: '#888888',
|
||||
},
|
||||
thumbText: {
|
||||
fontSize: 20,
|
||||
color: '#888888',
|
||||
},
|
||||
buttonContents: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginHorizontal: 5,
|
||||
marginVertical: 3,
|
||||
padding: 5,
|
||||
backgroundColor: '#EAEAEA',
|
||||
borderRadius: 3,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
img: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
marginHorizontal: 10,
|
||||
},
|
||||
section: {
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-start',
|
||||
padding: 6,
|
||||
backgroundColor: '#5890ff',
|
||||
},
|
||||
});
|
||||
|
||||
var animations = {
|
||||
layout: {
|
||||
spring: {
|
||||
duration: 0.75,
|
||||
create: {
|
||||
duration: 0.3,
|
||||
type: LayoutAnimation.Types.easeInEaseOut,
|
||||
property: LayoutAnimation.Properties.opacity,
|
||||
},
|
||||
update: {
|
||||
type: LayoutAnimation.Types.spring,
|
||||
springDamping: 0.4,
|
||||
},
|
||||
},
|
||||
easeInEaseOut: {
|
||||
duration: 0.3,
|
||||
create: {
|
||||
type: LayoutAnimation.Types.easeInEaseOut,
|
||||
property: LayoutAnimation.Properties.scaleXY,
|
||||
},
|
||||
update: {
|
||||
delay: 0.1,
|
||||
type: LayoutAnimation.Types.easeInEaseOut,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var layoutAnimationConfigs = [
|
||||
animations.layout.spring,
|
||||
animations.layout.easeInEaseOut,
|
||||
];
|
||||
|
||||
module.exports = ListViewPagingExample;
|
|
@ -24,6 +24,7 @@ var EXAMPLES = [
|
|||
require('./ExpandingTextExample'),
|
||||
require('./ImageExample'),
|
||||
require('./ListViewSimpleExample'),
|
||||
require('./ListViewPagingExample'),
|
||||
require('./NavigatorIOSExample'),
|
||||
require('./StatusBarIOSExample'),
|
||||
require('./PointerEventsExample'),
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule LayoutAnimation
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var PropTypes = require('ReactPropTypes');
|
||||
var RKUIManager = require('NativeModules').RKUIManager;
|
||||
|
||||
var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker');
|
||||
var keyMirror = require('keyMirror');
|
||||
|
||||
var Types = keyMirror({
|
||||
spring: true,
|
||||
linear: true,
|
||||
easeInEaseOut: true,
|
||||
easeIn: true,
|
||||
easeOut: true,
|
||||
});
|
||||
|
||||
var Properties = keyMirror({
|
||||
opacity: true,
|
||||
scaleXY: true,
|
||||
});
|
||||
|
||||
var animChecker = createStrictShapeTypeChecker({
|
||||
duration: PropTypes.number,
|
||||
delay: PropTypes.number,
|
||||
springDamping: PropTypes.number,
|
||||
initialVelocity: PropTypes.number,
|
||||
type: PropTypes.oneOf(
|
||||
Object.keys(Types)
|
||||
),
|
||||
property: PropTypes.oneOf( // Only applies to create/delete
|
||||
Object.keys(Properties)
|
||||
),
|
||||
});
|
||||
|
||||
var configChecker = createStrictShapeTypeChecker({
|
||||
duration: PropTypes.number.isRequired,
|
||||
create: animChecker,
|
||||
update: animChecker,
|
||||
delete: animChecker,
|
||||
});
|
||||
|
||||
var LayoutAnimation = {
|
||||
configureNext(config, onAnimationDidEnd, onError) {
|
||||
configChecker({config}, 'config', 'LayoutAnimation.configureNext');
|
||||
RKUIManager.configureNextLayoutAnimation(config, onAnimationDidEnd, onError);
|
||||
},
|
||||
create(duration, type, creationProp) {
|
||||
return {
|
||||
duration,
|
||||
create: {
|
||||
type,
|
||||
property: creationProp,
|
||||
},
|
||||
update: {
|
||||
type,
|
||||
},
|
||||
};
|
||||
},
|
||||
Types: Types,
|
||||
Properties: Properties,
|
||||
configChecker: configChecker,
|
||||
};
|
||||
|
||||
LayoutAnimation.Presets = {
|
||||
easeInEaseOut: LayoutAnimation.create(
|
||||
0.3, Types.easeInEaseOut, Properties.opacity
|
||||
),
|
||||
linear: LayoutAnimation.create(
|
||||
0.5, Types.linear, Properties.opacity
|
||||
),
|
||||
spring: {
|
||||
duration: 0.7,
|
||||
create: {
|
||||
type: Types.linear,
|
||||
property: Properties.opacity,
|
||||
},
|
||||
update: {
|
||||
type: Types.spring,
|
||||
springDamping: 0.4,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = LayoutAnimation;
|
|
@ -0,0 +1,250 @@
|
|||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule POPAnimationMixin
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var POPAnimation = require('POPAnimation');
|
||||
if (!POPAnimation) {
|
||||
// POP animation isn't available in the OSS fork - this is a temporary
|
||||
// workaround to enable its availability to be determined at runtime.
|
||||
module.exports = null;
|
||||
} else {
|
||||
|
||||
var invariant = require('invariant');
|
||||
var warning = require('warning');
|
||||
|
||||
var POPAnimationMixin = {
|
||||
/**
|
||||
* Different ways to interpolate between beginning and end states
|
||||
* of properties during animation, such as spring, linear, and decay.
|
||||
*/
|
||||
AnimationTypes: POPAnimation.Types,
|
||||
AnimationProperties: POPAnimation.Properties,
|
||||
|
||||
getInitialState: function(): Object {
|
||||
return {
|
||||
_currentAnimationsByNodeHandle: {},
|
||||
};
|
||||
},
|
||||
|
||||
_ensureBookkeepingSetup: function(nodeHandle: any) {
|
||||
if (!this.state._currentAnimationsByNodeHandle[nodeHandle]) {
|
||||
this.state._currentAnimationsByNodeHandle[nodeHandle] = [];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Start animating the View with ref `refKey`.
|
||||
*
|
||||
* @param {key} refKey The key to reference the View to be animated.
|
||||
*
|
||||
* @param {number|Object} anim Either the identifier returned by
|
||||
* POPAnimation.create* or an object defining all the necessary
|
||||
* properties of the animation you wish to start (including type, matching
|
||||
* an entry in AnimationTypes).
|
||||
*
|
||||
* @param {func} doneCallback A callback fired when the animation is done, and
|
||||
* is passed a `finished` param that indicates whether the animation
|
||||
* completely finished, or was interrupted.
|
||||
*/
|
||||
startAnimation: function(
|
||||
refKey: string,
|
||||
anim: number | {type: number; property: number;},
|
||||
doneCallback: (finished: bool) => void
|
||||
) {
|
||||
var animID: number = 0;
|
||||
if (typeof anim === 'number') {
|
||||
animID = anim;
|
||||
} else {
|
||||
invariant(
|
||||
anim instanceof Object &&
|
||||
anim.type !== undefined &&
|
||||
anim.property !== undefined,
|
||||
'Animation definitions must specify a type of animation and a ' +
|
||||
'property to animate.'
|
||||
);
|
||||
animID = POPAnimation.createAnimation(anim.type, anim);
|
||||
}
|
||||
invariant(
|
||||
this.refs[refKey],
|
||||
'Invalid refKey ' + refKey + ' for anim:\n' + JSON.stringify(anim) +
|
||||
'\nvalid refs: ' + JSON.stringify(Object.keys(this.refs))
|
||||
);
|
||||
var refNodeHandle = this.refs[refKey].getNodeHandle();
|
||||
this.startAnimationWithNodeHandle(refNodeHandle, animID, doneCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts an animation on a native node.
|
||||
*
|
||||
* @param {NodeHandle} nodeHandle Handle to underlying native node.
|
||||
* @see `startAnimation`.
|
||||
*/
|
||||
startAnimationWithNodeHandle: function(
|
||||
nodeHandle: any,
|
||||
animID: number,
|
||||
doneCallback: (finished: bool) => void
|
||||
) {
|
||||
this._ensureBookkeepingSetup(nodeHandle);
|
||||
var animations = this.state._currentAnimationsByNodeHandle[nodeHandle];
|
||||
var animIndex = animations.length;
|
||||
animations.push(animID);
|
||||
var cleanupWrapper = (finished) => {
|
||||
if (!this.isMounted()) {
|
||||
return;
|
||||
}
|
||||
animations[animIndex] = 0; // zero it out so we don't try to stop it
|
||||
var allDone = true;
|
||||
for (var ii = 0; ii < animations.length; ii++) {
|
||||
if (animations[ii]) {
|
||||
allDone = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allDone) {
|
||||
this.state._currentAnimationsByNodeHandle[nodeHandle] = undefined;
|
||||
}
|
||||
doneCallback && doneCallback(finished);
|
||||
};
|
||||
POPAnimation.addAnimation(nodeHandle, animID, cleanupWrapper);
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts multiple animations with one shared callback that is called when all
|
||||
* animations complete.
|
||||
*
|
||||
* @param {Array(Object} animations Array of objects defining all the
|
||||
* animations to start, each with shape `{ref|nodeHandle, anim}`.
|
||||
* @param {func} onSuccess A callback fired when all animations have returned,
|
||||
* and is passed a finished arg that is true if all animations finished
|
||||
* completely.
|
||||
* @param {func} onFailure Not supported yet.
|
||||
*/
|
||||
startAnimations: function(
|
||||
animations: Array<Object>,
|
||||
onSuccess: (finished: boolean) => void,
|
||||
onFailure: () => void
|
||||
) {
|
||||
var numReturned = 0;
|
||||
var numFinished = 0;
|
||||
var numAnimations = animations.length;
|
||||
var metaCallback = (finished) => {
|
||||
if (finished) {
|
||||
++numFinished;
|
||||
}
|
||||
if (++numReturned === numAnimations) {
|
||||
onSuccess && onSuccess(numFinished === numAnimations);
|
||||
}
|
||||
};
|
||||
animations.forEach((anim) => {
|
||||
warning(
|
||||
anim.ref != null || anim.nodeHandle != null &&
|
||||
!anim.ref !== !anim.nodeHandle,
|
||||
'Animations must be specified with either ref xor nodeHandle'
|
||||
);
|
||||
if (anim.ref) {
|
||||
this.startAnimation(anim.ref, anim.anim, metaCallback);
|
||||
} else if (anim.nodeHandle) {
|
||||
this.startAnimationWithNodeHandle(anim.nodeHandle, anim.anim, metaCallback);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop any and all animations operating on the View with native node handle
|
||||
* `nodeHandle`.
|
||||
*
|
||||
* @param {NodeHandle} component The instance to stop animations
|
||||
* on. Do not pass a composite component.
|
||||
*/
|
||||
stopNodeHandleAnimations: function(nodeHandle: any) {
|
||||
if (!this.state._currentAnimationsByNodeHandle[nodeHandle]) {
|
||||
return;
|
||||
}
|
||||
var anims = this.state._currentAnimationsByNodeHandle[nodeHandle];
|
||||
for (var i = 0; i < anims.length; i++) {
|
||||
var anim = anims[i];
|
||||
if (anim) {
|
||||
// Note: Converting the string key to a number `nodeHandle`.
|
||||
POPAnimation.removeAnimation(+nodeHandle, anim);
|
||||
}
|
||||
}
|
||||
this.state._currentAnimationsByNodeHandle[nodeHandle] = undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop any and all animations operating on the View with ref `refKey`.
|
||||
*
|
||||
* @param {key} refKey The key to reference the View to be animated.
|
||||
*/
|
||||
stopAnimations: function(refKey: string) {
|
||||
invariant(this.refs[refKey], 'invalid ref');
|
||||
this.stopNodeHandleAnimations(this.refs[refKey].getNodeHandle());
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop any and all animations created by this component on itself and
|
||||
* subviews.
|
||||
*/
|
||||
stopAllAnimations: function() {
|
||||
for (var nodeHandle in this.state._currentAnimationsByNodeHandle) {
|
||||
this.stopNodeHandleAnimations(nodeHandle);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Animates size and position of a view referenced by `refKey` to a specific
|
||||
* frame.
|
||||
*
|
||||
* @param {key} refKey ref key for view to animate.
|
||||
* @param {Object} frame The frame to animate the view to, specified as {left,
|
||||
* top, width, height}.
|
||||
* @param {const} type What type of interpolation to use, selected from
|
||||
* `inperpolationTypes`.
|
||||
* @param {Object} event Event encapsulating synthetic and native data that
|
||||
* may have triggered this animation. Velocity is extracted from it if
|
||||
* possible and applied to the animation.
|
||||
* @param {func} doneCallback A callback fired when the animation is done, and
|
||||
* is passed a `finished` param that indicates whether the animation
|
||||
* completely finished, or was interrupted.
|
||||
*/
|
||||
animateToFrame: function(
|
||||
refKey: string,
|
||||
frame: {left: number; top: number; width: number; height: number;},
|
||||
type: number,
|
||||
velocity: number,
|
||||
doneCallback: (finished: boolean) => void
|
||||
) {
|
||||
var animFrame = { // Animations use a centered coordinate system.
|
||||
x: frame.left + frame.width / 2,
|
||||
y: frame.top + frame.height / 2,
|
||||
w: frame.width,
|
||||
h: frame.height
|
||||
};
|
||||
frame = undefined;
|
||||
var velocity = velocity || [0, 0];
|
||||
var posAnim = POPAnimation.createAnimation(type, {
|
||||
property: POPAnimation.Properties.position,
|
||||
toValue: [animFrame.x, animFrame.y],
|
||||
velocity: velocity,
|
||||
});
|
||||
var sizeAnim = POPAnimation.createAnimation(type, {
|
||||
property: POPAnimation.Properties.size,
|
||||
toValue: [animFrame.w, animFrame.h]
|
||||
});
|
||||
this.startAnimation(refKey, posAnim, doneCallback);
|
||||
this.startAnimation(refKey, sizeAnim);
|
||||
},
|
||||
|
||||
// Cleanup any potentially leaked animations.
|
||||
componentWillUnmount: function() {
|
||||
this.stopAllAnimations();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = POPAnimationMixin;
|
||||
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule POPAnimation
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var RKPOPAnimationManager = require('NativeModulesDeprecated').RKPOPAnimationManager;
|
||||
if (!RKPOPAnimationManager) {
|
||||
// POP animation isn't available in the OSS fork - this is a temporary
|
||||
// workaround to enable its availability to be determined at runtime.
|
||||
module.exports = null;
|
||||
} else {
|
||||
|
||||
var ReactPropTypes = require('ReactPropTypes');
|
||||
var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker');
|
||||
var getObjectValues = require('getObjectValues');
|
||||
var invariant = require('invariant');
|
||||
var merge = require('merge');
|
||||
|
||||
var RKTypes = RKPOPAnimationManager.Types;
|
||||
var RKProperties = RKPOPAnimationManager.Properties;
|
||||
|
||||
var Properties = {
|
||||
bounds: RKProperties.bounds,
|
||||
opacity: RKProperties.opacity,
|
||||
position: RKProperties.position,
|
||||
positionX: RKProperties.positionX,
|
||||
positionY: RKProperties.positionY,
|
||||
zPosition: RKProperties.zPosition,
|
||||
rotation: RKProperties.rotation,
|
||||
rotationX: RKProperties.rotationX,
|
||||
rotationY: RKProperties.rotationY,
|
||||
scaleX: RKProperties.scaleX,
|
||||
scaleXY: RKProperties.scaleXY,
|
||||
scaleY: RKProperties.scaleY,
|
||||
shadowColor: RKProperties.shadowColor,
|
||||
shadowOffset: RKProperties.shadowOffset,
|
||||
shadowOpacity: RKProperties.shadowOpacity,
|
||||
shadowRadius: RKProperties.shadowRadius,
|
||||
size: RKProperties.size,
|
||||
subscaleXY: RKProperties.subscaleXY,
|
||||
subtranslationX: RKProperties.subtranslationX,
|
||||
subtranslationXY: RKProperties.subtranslationXY,
|
||||
subtranslationY: RKProperties.subtranslationY,
|
||||
subtranslationZ: RKProperties.subtranslationZ,
|
||||
translationX: RKProperties.translationX,
|
||||
translationXY: RKProperties.translationXY,
|
||||
translationY: RKProperties.translationY,
|
||||
translationZ: RKProperties.translationZ,
|
||||
};
|
||||
|
||||
var Types = {
|
||||
decay: RKTypes.decay,
|
||||
easeIn: RKTypes.easeIn,
|
||||
easeInEaseOut: RKTypes.easeInEaseOut,
|
||||
easeOut: RKTypes.easeOut,
|
||||
linear: RKTypes.linear,
|
||||
spring: RKTypes.spring,
|
||||
};
|
||||
|
||||
var POPAnimation = {
|
||||
Types: Types,
|
||||
Properties: Properties,
|
||||
|
||||
attributeChecker: createStrictShapeTypeChecker({
|
||||
type: ReactPropTypes.oneOf(getObjectValues(Types)),
|
||||
property: ReactPropTypes.oneOf(getObjectValues(Properties)),
|
||||
fromValue: ReactPropTypes.any,
|
||||
toValue: ReactPropTypes.any,
|
||||
duration: ReactPropTypes.any,
|
||||
velocity: ReactPropTypes.any,
|
||||
deceleration: ReactPropTypes.any,
|
||||
springBounciness: ReactPropTypes.any,
|
||||
dynamicsFriction: ReactPropTypes.any,
|
||||
dynamicsMass: ReactPropTypes.any,
|
||||
dynamicsTension: ReactPropTypes.any,
|
||||
}),
|
||||
|
||||
lastUsedTag: 0,
|
||||
allocateTagForAnimation: function() {
|
||||
return ++this.lastUsedTag;
|
||||
},
|
||||
|
||||
createAnimation: function(typeName, attrs) {
|
||||
var tag = this.allocateTagForAnimation();
|
||||
|
||||
if (__DEV__) {
|
||||
POPAnimation.attributeChecker(
|
||||
{attrs},
|
||||
'attrs',
|
||||
'POPAnimation.createAnimation'
|
||||
);
|
||||
POPAnimation.attributeChecker(
|
||||
{attrs: {type: typeName}},
|
||||
'attrs',
|
||||
'POPAnimation.createAnimation'
|
||||
);
|
||||
}
|
||||
|
||||
RKPOPAnimationManager.createAnimationInternal(tag, typeName, attrs);
|
||||
return tag;
|
||||
},
|
||||
|
||||
createSpringAnimation: function(attrs) {
|
||||
return this.createAnimation(this.Types.spring, attrs);
|
||||
},
|
||||
|
||||
createDecayAnimation: function(attrs) {
|
||||
return this.createAnimation(this.Types.decay, attrs);
|
||||
},
|
||||
|
||||
createLinearAnimation: function(attrs) {
|
||||
return this.createAnimation(this.Types.linear, attrs);
|
||||
},
|
||||
|
||||
createEaseInAnimation: function(attrs) {
|
||||
return this.createAnimation(this.Types.easeIn, attrs);
|
||||
},
|
||||
|
||||
createEaseOutAnimation: function(attrs) {
|
||||
return this.createAnimation(this.Types.easeOut, attrs);
|
||||
},
|
||||
|
||||
createEaseInEaseOutAnimation: function(attrs) {
|
||||
return this.createAnimation(this.Types.easeInEaseOut, attrs);
|
||||
},
|
||||
|
||||
addAnimation: function(nodeHandle, anim, callback) {
|
||||
RKPOPAnimationManager.addAnimation(nodeHandle, anim, callback);
|
||||
},
|
||||
|
||||
removeAnimation: function(nodeHandle, anim) {
|
||||
RKPOPAnimationManager.removeAnimation(nodeHandle, anim);
|
||||
},
|
||||
};
|
||||
|
||||
// Make sure that we correctly propagate RKPOPAnimationManager constants
|
||||
// to POPAnimation
|
||||
if (__DEV__) {
|
||||
var allProperties = merge(
|
||||
RKPOPAnimationManager.Properties,
|
||||
RKPOPAnimationManager.Properties
|
||||
);
|
||||
for (var key in allProperties) {
|
||||
invariant(
|
||||
POPAnimation.Properties[key] === RKPOPAnimationManager.Properties[key],
|
||||
'POPAnimation doesn\'t copy property ' + key + ' correctly'
|
||||
);
|
||||
}
|
||||
|
||||
var allTypes = merge(
|
||||
RKPOPAnimationManager.Types,
|
||||
RKPOPAnimationManager.Types
|
||||
);
|
||||
for (var key in allTypes) {
|
||||
invariant(
|
||||
POPAnimation.Types[key] === RKPOPAnimationManager.Types[key],
|
||||
'POPAnimation doesn\'t copy type ' + key + ' correctly'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = POPAnimation;
|
||||
|
||||
}
|
|
@ -25,7 +25,6 @@ var invariant = require('invariant');
|
|||
var merge = require('merge');
|
||||
var nativePropType = require('nativePropType');
|
||||
var validAttributesFromPropTypes = require('validAttributesFromPropTypes');
|
||||
var warning = require('warning');
|
||||
|
||||
var PropTypes = React.PropTypes;
|
||||
|
||||
|
@ -194,14 +193,19 @@ var ScrollView = React.createClass({
|
|||
);
|
||||
}
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
this.props.onScroll && !this.props.throttleScrollCallbackMS,
|
||||
if (this.props.onScroll && !this.props.throttleScrollCallbackMS) {
|
||||
var onScroll = this.props.onScroll;
|
||||
this.props.onScroll = function() {
|
||||
console.log(
|
||||
'You specified `onScroll` on a <ScrollView> but not ' +
|
||||
'`throttleScrollCallbackMS`. You will only receive one event. ' +
|
||||
'Using `16` you get all the events but be aware that it may cause ' +
|
||||
'frame drops, use a bigger number if you don\'t need as much ' +
|
||||
'precision.'
|
||||
'Using `16` you get all the events but be aware that it may ' +
|
||||
'cause frame drops, use a bigger number if you don\'t need as ' +
|
||||
'much precision.'
|
||||
);
|
||||
onScroll.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var contentContainer =
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule TouchableOpacity
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var POPAnimationMixin = require('POPAnimationMixin');
|
||||
var React = require('React');
|
||||
var Touchable = require('Touchable');
|
||||
|
||||
var cloneWithProps = require('cloneWithProps');
|
||||
var ensureComponentIsNative = require('ensureComponentIsNative');
|
||||
var keyOf = require('keyOf');
|
||||
var onlyChild = require('onlyChild');
|
||||
|
||||
/**
|
||||
* TouchableOpacity - A wrapper for making views respond properly to touches.
|
||||
* On press down, the opacity of the wrapped view is decreased, dimming it.
|
||||
* This is done without actually changing the view hierarchy, and in general is
|
||||
* easy to add to an app without weird side-effects. Example:
|
||||
*
|
||||
* renderButton: function() {
|
||||
* return (
|
||||
* <TouchableOpacity onPress={this._onPressButton}>
|
||||
* <Image
|
||||
* style={styles.button}
|
||||
* source={ix('myButton')}
|
||||
* />
|
||||
* </View>
|
||||
* );
|
||||
* },
|
||||
*
|
||||
* More example code in TouchableExample.js, and more in-depth discussion in
|
||||
* Touchable.js. See also TouchableHighlight.js and
|
||||
* TouchableWithoutFeedback.js.
|
||||
*/
|
||||
|
||||
var TouchableOpacity = React.createClass({
|
||||
mixins: [Touchable.Mixin, NativeMethodsMixin, POPAnimationMixin],
|
||||
|
||||
propTypes: {
|
||||
/**
|
||||
* Called when the touch is released, but not if cancelled (e.g. by
|
||||
* a scroll that steals the responder lock).
|
||||
*/
|
||||
onPress: React.PropTypes.func,
|
||||
/**
|
||||
* Determines what the opacity of the wrapped view should be when touch is
|
||||
* active.
|
||||
*/
|
||||
activeOpacity: React.PropTypes.number,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
activeOpacity: 0.5,
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return this.touchableGetInitialState();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
ensureComponentIsNative(this.refs[CHILD_REF]);
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
ensureComponentIsNative(this.refs[CHILD_REF]);
|
||||
},
|
||||
|
||||
setOpacityTo: function(value) {
|
||||
if (POPAnimationMixin) {
|
||||
// Reset with animation if POP is available
|
||||
this.stopAllAnimations();
|
||||
var anim = {
|
||||
type: this.AnimationTypes.linear,
|
||||
property: this.AnimationProperties.opacity,
|
||||
toValue: value,
|
||||
};
|
||||
this.startAnimation(CHILD_REF, anim);
|
||||
} else {
|
||||
// Reset immediately if POP is unavailable
|
||||
this.refs[CHILD_REF].setNativeProps({
|
||||
opacity: value
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
|
||||
* defined on your component.
|
||||
*/
|
||||
touchableHandleActivePressIn: function() {
|
||||
this.refs[CHILD_REF].setNativeProps({
|
||||
opacity: this.props.activeOpacity
|
||||
});
|
||||
},
|
||||
|
||||
touchableHandleActivePressOut: function() {
|
||||
this.setOpacityTo(1.0);
|
||||
},
|
||||
|
||||
touchableHandlePress: function() {
|
||||
this.setOpacityTo(1.0);
|
||||
this.props.onPress && this.props.onPress();
|
||||
},
|
||||
|
||||
touchableGetPressRectOffset: function() {
|
||||
return PRESS_RECT_OFFSET; // Always make sure to predeclare a constant!
|
||||
},
|
||||
|
||||
touchableGetHighlightDelayMS: function() {
|
||||
return 0;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return cloneWithProps(onlyChild(this.props.children), {
|
||||
ref: CHILD_REF,
|
||||
accessible: true,
|
||||
testID: this.props.testID,
|
||||
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
|
||||
onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
|
||||
onResponderGrant: this.touchableHandleResponderGrant,
|
||||
onResponderMove: this.touchableHandleResponderMove,
|
||||
onResponderRelease: this.touchableHandleResponderRelease,
|
||||
onResponderTerminate: this.touchableHandleResponderTerminate,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* When the scroll view is disabled, this defines how far your touch may move
|
||||
* off of the button, before deactivating the button. Once deactivated, try
|
||||
* moving it back and you'll see that the button is once again reactivated!
|
||||
* Move it back and forth several times while the scroll view is disabled.
|
||||
*/
|
||||
var PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
|
||||
|
||||
var CHILD_REF = keyOf({childRef: null});
|
||||
|
||||
module.exports = TouchableOpacity;
|
|
@ -9,6 +9,7 @@ var NativeModules = require('NativeModules');
|
|||
var NativeModulesDeprecated = require('NativeModulesDeprecated');
|
||||
var RKUIManager = NativeModules.RKUIManager;
|
||||
var RKUIManagerDeprecated = NativeModulesDeprecated.RKUIManager;
|
||||
var RKPOPAnimationManagerDeprecated = NativeModulesDeprecated.RKPOPAnimationManager;
|
||||
var TextInputState = require('TextInputState');
|
||||
|
||||
var flattenStyle = require('flattenStyle');
|
||||
|
@ -19,19 +20,19 @@ var animationIDInvariant = function(funcName, anim) {
|
|||
invariant(
|
||||
anim,
|
||||
funcName + ' must be called with a valid animation ID returned from' +
|
||||
' ReactIOSAnimation.createAnimation, received: "' + anim + '"'
|
||||
' POPAnimation.createAnimation, received: "' + anim + '"'
|
||||
);
|
||||
};
|
||||
|
||||
var NativeMethodsMixin = {
|
||||
addAnimation: function(anim, callback) {
|
||||
animationIDInvariant('addAnimation', anim);
|
||||
RKUIManagerDeprecated.addAnimation(this.getNodeHandle(), anim, callback);
|
||||
RKPOPAnimationManagerDeprecated.addAnimation(this.getNodeHandle(), anim, callback);
|
||||
},
|
||||
|
||||
removeAnimation: function(anim) {
|
||||
animationIDInvariant('removeAnimation', anim);
|
||||
RKUIManagerDeprecated.removeAnimation(this.getNodeHandle(), anim);
|
||||
RKPOPAnimationManagerDeprecated.removeAnimation(this.getNodeHandle(), anim);
|
||||
},
|
||||
|
||||
measure: function(callback) {
|
||||
|
|
|
@ -169,20 +169,12 @@ var ReactIOSEventEmitter = merge(ReactEventEmitterMixin, {
|
|||
var target = nativeEvent.target;
|
||||
if (target !== null && target !== undefined) {
|
||||
if (target < ReactIOSTagHandles.tagsStartAt) {
|
||||
// When we get multiple touches at the same time, only the first touch
|
||||
// actually has a view attached to it. The rest of the touches do not.
|
||||
// This is presumably because iOS doesn't want to send touch events to
|
||||
// two views for a single multi touch. Therefore this warning is only
|
||||
// appropriate when it happens to the first touch. (hence jj === 0)
|
||||
if (__DEV__) {
|
||||
if (jj === 0) {
|
||||
warning(
|
||||
false,
|
||||
'A view is reporting that a touch occured on tag zero.'
|
||||
);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
rootNodeID = NodeHandle.getRootNodeID(target);
|
||||
}
|
||||
|
|
|
@ -9,20 +9,35 @@ var ReactDefaultPerf = require('ReactDefaultPerf');
|
|||
var ReactPerf = require('ReactPerf');
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
var perfModules = [];
|
||||
var enabled = false;
|
||||
|
||||
var RCTRenderingPerf = {
|
||||
// Once perf is enabled, it stays enabled
|
||||
toggle: function() {
|
||||
if (ReactPerf.enableMeasure) {
|
||||
console.log('Render perfomance measurements enabled');
|
||||
enabled = true;
|
||||
},
|
||||
|
||||
start: function() {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReactDefaultPerf.start();
|
||||
perfModules.forEach((module) => module.start());
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReactDefaultPerf.stop();
|
||||
ReactDefaultPerf.printInclusive();
|
||||
ReactDefaultPerf.printWasted();
|
||||
perfModules.forEach((module) => module.stop());
|
||||
} else {
|
||||
ReactDefaultPerf.start();
|
||||
console.log('Render perfomance measurements started');
|
||||
perfModules.forEach((module) => module.start());
|
||||
}
|
||||
},
|
||||
|
||||
register: function(module) {
|
||||
|
|
|
@ -10,6 +10,7 @@ var ReactNative = {
|
|||
Bundler: require('Bundler'),
|
||||
ExpandingText: require('ExpandingText'),
|
||||
Image: require('Image'),
|
||||
LayoutAnimation: require('LayoutAnimation'),
|
||||
ListView: require('ListView'),
|
||||
ListViewDataSource: require('ListViewDataSource'),
|
||||
NavigatorIOS: require('NavigatorIOS'),
|
||||
|
@ -22,6 +23,7 @@ var ReactNative = {
|
|||
TextInput: require('TextInput'),
|
||||
TimerMixin: require('TimerMixin'),
|
||||
TouchableHighlight: require('TouchableHighlight'),
|
||||
TouchableOpacity: require('TouchableOpacity'),
|
||||
TouchableWithoutFeedback: require('TouchableWithoutFeedback'),
|
||||
View: require('View'),
|
||||
invariant: require('invariant'),
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, RCTAnimationType) {
|
||||
RCTAnimationTypeSpring = 0,
|
||||
RCTAnimationTypeLinear,
|
||||
RCTAnimationTypeEaseIn,
|
||||
RCTAnimationTypeEaseOut,
|
||||
RCTAnimationTypeEaseInEaseOut,
|
||||
};
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#import "Layout.h"
|
||||
#import "RCTPointerEvents.h"
|
||||
#import "RCTAnimationType.h"
|
||||
|
||||
/**
|
||||
* This class provides a collection of conversion functions for mapping
|
||||
|
@ -64,6 +65,7 @@
|
|||
+ (css_wrap_type_t)css_wrap_type_t:(id)json;
|
||||
|
||||
+ (RCTPointerEvents)RCTPointerEvents:(id)json;
|
||||
+ (RCTAnimationType)RCTAnimationType:(id)json;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ NSString *const RCTBoldFontWeight = @"bold";
|
|||
return code; \
|
||||
} \
|
||||
@catch (__unused NSException *e) { \
|
||||
RCTLogMustFix(@"JSON value '%@' of type '%@' cannot be converted to '%s'", \
|
||||
RCTLogError(@"JSON value '%@' of type '%@' cannot be converted to '%s'", \
|
||||
json, [json class], #type); \
|
||||
json = nil; \
|
||||
return code; \
|
||||
|
@ -45,15 +45,15 @@ RCT_CONVERTER_CUSTOM(type, name, [json getter])
|
|||
if ([[mapping allValues] containsObject:json] || [json getter] == default) { \
|
||||
return [json getter]; \
|
||||
} \
|
||||
RCTLogMustFix(@"Invalid %s '%@'. should be one of: %@", #type, json, [mapping allValues]); \
|
||||
RCTLogError(@"Invalid %s '%@'. should be one of: %@", #type, json, [mapping allValues]); \
|
||||
return default; \
|
||||
} \
|
||||
if (![json isKindOfClass:[NSString class]]) { \
|
||||
RCTLogMustFix(@"Expected NSNumber or NSString for %s, received %@: %@", #type, [json class], json); \
|
||||
RCTLogError(@"Expected NSNumber or NSString for %s, received %@: %@", #type, [json class], json); \
|
||||
} \
|
||||
id value = mapping[json]; \
|
||||
if(!value && [json description].length > 0) { \
|
||||
RCTLogMustFix(@"Invalid %s '%@'. should be one of: %@", #type, json, [mapping allKeys]); \
|
||||
RCTLogError(@"Invalid %s '%@'. should be one of: %@", #type, json, [mapping allKeys]); \
|
||||
} \
|
||||
return value ? [value getter] : default; \
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ RCT_CONVERTER_CUSTOM(type, name, [json getter])
|
|||
type result; \
|
||||
if ([json isKindOfClass:[NSArray class]]) { \
|
||||
if ([json count] != count) { \
|
||||
RCTLogMustFix(@"Expected array with count %zd, but count is %zd: %@", count, [json count], json); \
|
||||
RCTLogError(@"Expected array with count %zd, but count is %zd: %@", count, [json count], json); \
|
||||
} else { \
|
||||
for (NSUInteger i = 0; i < count; i++) { \
|
||||
((CGFloat *)&result)[i] = [json[i] doubleValue]; \
|
||||
|
@ -80,7 +80,7 @@ RCT_CONVERTER_CUSTOM(type, name, [json getter])
|
|||
} \
|
||||
} else { \
|
||||
if (![json isKindOfClass:[NSDictionary class]]) { \
|
||||
RCTLogMustFix(@"Expected NSArray or NSDictionary for %s, received %@: %@", #type, [json class], json); \
|
||||
RCTLogError(@"Expected NSArray or NSDictionary for %s, received %@: %@", #type, [json class], json); \
|
||||
} else { \
|
||||
for (NSUInteger i = 0; i < count; i++) { \
|
||||
((CGFloat *)&result)[i] = [json[fields[i]] doubleValue]; \
|
||||
|
@ -90,7 +90,7 @@ RCT_CONVERTER_CUSTOM(type, name, [json getter])
|
|||
return result; \
|
||||
} \
|
||||
@catch (__unused NSException *e) { \
|
||||
RCTLogMustFix(@"JSON value '%@' cannot be converted to '%s'", json, #type); \
|
||||
RCTLogError(@"JSON value '%@' cannot be converted to '%s'", json, #type); \
|
||||
type result; \
|
||||
return result; \
|
||||
} \
|
||||
|
@ -111,7 +111,7 @@ RCT_CONVERTER_CUSTOM(NSUInteger, NSUInteger, [json unsignedIntegerValue])
|
|||
+ (NSURL *)NSURL:(id)json
|
||||
{
|
||||
if (![json isKindOfClass:[NSString class]]) {
|
||||
RCTLogMustFix(@"Expected NSString for NSURL, received %@: %@", [json class], json);
|
||||
RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json class], json);
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
@ -376,10 +376,10 @@ RCT_STRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty"]
|
|||
} else if ([colorString hasPrefix:@"rgb("]) {
|
||||
sscanf([colorString UTF8String], "rgb(%zd,%zd,%zd)", &red, &green, &blue);
|
||||
} else {
|
||||
RCTLogMustFix(@"Unrecognized color format '%@', must be one of #hex|rgba|rgb", colorString);
|
||||
RCTLogError(@"Unrecognized color format '%@', must be one of #hex|rgba|rgb", colorString);
|
||||
}
|
||||
if (red == -1 || green == -1 || blue == -1 || alpha > 1.0 || alpha < 0.0) {
|
||||
RCTLogMustFix(@"Invalid color string '%@'", colorString);
|
||||
RCTLogError(@"Invalid color string '%@'", colorString);
|
||||
} else {
|
||||
color = [UIColor colorWithRed:red / 255.0 green:green / 255.0 blue:blue / 255.0 alpha:alpha];
|
||||
}
|
||||
|
@ -388,7 +388,7 @@ RCT_STRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty"]
|
|||
|
||||
if ([json count] < 3 || [json count] > 4) {
|
||||
|
||||
RCTLogMustFix(@"Expected array with count 3 or 4, but count is %zd: %@", [json count], json);
|
||||
RCTLogError(@"Expected array with count 3 or 4, but count is %zd: %@", [json count], json);
|
||||
|
||||
} else {
|
||||
|
||||
|
@ -409,7 +409,7 @@ RCT_STRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty"]
|
|||
|
||||
} else if (json && ![json isKindOfClass:[NSNull class]]) {
|
||||
|
||||
RCTLogMustFix(@"Expected NSArray, NSDictionary or NSString for UIColor, received %@: %@", [json class], json);
|
||||
RCTLogError(@"Expected NSArray, NSDictionary or NSString for UIColor, received %@: %@", [json class], json);
|
||||
}
|
||||
|
||||
// Default color
|
||||
|
@ -509,7 +509,7 @@ RCT_STRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty"]
|
|||
+ (UIImage *)UIImage:(id)json
|
||||
{
|
||||
if (![json isKindOfClass:[NSString class]]) {
|
||||
RCTLogMustFix(@"Expected NSString for UIImage, received %@: %@", [json class], json);
|
||||
RCTLogError(@"Expected NSString for UIImage, received %@: %@", [json class], json);
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
@ -657,6 +657,14 @@ RCT_ENUM_CONVERTER(RCTPointerEvents, (@{
|
|||
@"boxnone": @(RCTPointerEventsBoxNone)
|
||||
}), RCTPointerEventsUnspecified, integerValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(RCTAnimationType, (@{
|
||||
@"spring": @(RCTAnimationTypeSpring),
|
||||
@"linear": @(RCTAnimationTypeLinear),
|
||||
@"easeIn": @(RCTAnimationTypeEaseIn),
|
||||
@"easeOut": @(RCTAnimationTypeEaseOut),
|
||||
@"easeInEaseOut": @(RCTAnimationTypeEaseInEaseOut),
|
||||
}), RCTAnimationTypeEaseInEaseOut, integerValue)
|
||||
|
||||
@end
|
||||
|
||||
static NSString *RCTGuessTypeEncoding(id target, NSString *key, id value, NSString *encoding)
|
||||
|
@ -832,6 +840,9 @@ BOOL RCTSetProperty(id target, NSString *keypath, id value)
|
|||
@"extAlignment": ^(id val) {
|
||||
return [RCTConvert NSTextAlignment:val];
|
||||
},
|
||||
@"ointerEvents": ^(id val) {
|
||||
return [RCTConvert RCTPointerEvents:val];
|
||||
},
|
||||
};
|
||||
});
|
||||
for (NSString *subkey in converters) {
|
||||
|
|
|
@ -6,15 +6,11 @@
|
|||
#import "RCTInvalidating.h"
|
||||
|
||||
@class RCTRootView;
|
||||
@class RCTShadowView;
|
||||
@class RCTSparseArray;
|
||||
|
||||
@protocol RCTScrollableProtocol;
|
||||
|
||||
@interface RCTUIManager : NSObject <RCTBridgeModule, RCTInvalidating>
|
||||
|
||||
@property (nonatomic, strong) RCTSparseArray *shadowViewRegistry;
|
||||
@property (nonatomic, strong) RCTSparseArray *viewRegistry;
|
||||
@property (nonatomic, weak) id<RCTScrollableProtocol> mainScrollView;
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <objc/message.h>
|
||||
#import <pthread.h>
|
||||
|
||||
#import "Layout.h"
|
||||
#import "RCTAnimationType.h"
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
|
@ -22,8 +22,6 @@
|
|||
#import "RCTViewManager.h"
|
||||
#import "UIView+ReactKit.h"
|
||||
|
||||
@class RCTAnimationConfig;
|
||||
|
||||
typedef void (^react_view_node_block_t)(id<RCTViewNodeProtocol>);
|
||||
|
||||
static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_block_t block)
|
||||
|
@ -75,40 +73,151 @@ static NSDictionary *RCTViewModuleClasses(void)
|
|||
return modules;
|
||||
}
|
||||
|
||||
@interface RCTAnimation : NSObject
|
||||
|
||||
@property (nonatomic, readonly) NSTimeInterval duration;
|
||||
@property (nonatomic, readonly) NSTimeInterval delay;
|
||||
@property (nonatomic, readonly, copy) NSString *property;
|
||||
@property (nonatomic, readonly) id fromValue;
|
||||
@property (nonatomic, readonly) id toValue;
|
||||
@property (nonatomic, readonly) CGFloat springDamping;
|
||||
@property (nonatomic, readonly) CGFloat initialVelocity;
|
||||
@property (nonatomic, readonly) RCTAnimationType animationType;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTAnimation
|
||||
|
||||
UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType type)
|
||||
{
|
||||
switch (type) {
|
||||
case RCTAnimationTypeLinear:
|
||||
return UIViewAnimationCurveLinear;
|
||||
case RCTAnimationTypeEaseIn:
|
||||
return UIViewAnimationCurveEaseIn;
|
||||
case RCTAnimationTypeEaseOut:
|
||||
return UIViewAnimationCurveEaseOut;
|
||||
case RCTAnimationTypeEaseInEaseOut:
|
||||
return UIViewAnimationCurveEaseInOut;
|
||||
default:
|
||||
RCTCAssert(NO, @"Unsupported animation type %zd", type);
|
||||
return UIViewAnimationCurveEaseInOut;
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)initWithDuration:(NSTimeInterval)duration dictionary:(NSDictionary *)config
|
||||
{
|
||||
if (!config) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ((self = [super init])) {
|
||||
_property = [RCTConvert NSString:config[@"property"]];
|
||||
|
||||
// TODO: this should be provided in ms, not seconds
|
||||
_duration = [RCTConvert NSTimeInterval:config[@"duration"]] ?: duration;
|
||||
_delay = [RCTConvert NSTimeInterval:config[@"delay"]];
|
||||
_animationType = [RCTConvert RCTAnimationType:config[@"type"]];
|
||||
if (_animationType == RCTAnimationTypeSpring) {
|
||||
_springDamping = [RCTConvert CGFloat:config[@"springDamping"]];
|
||||
_initialVelocity = [RCTConvert CGFloat:config[@"initialVelocity"]];
|
||||
}
|
||||
_fromValue = config[@"fromValue"];
|
||||
_toValue = config[@"toValue"];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)performAnimations:(void (^)(void))animations
|
||||
withCompletionBlock:(void (^)(BOOL completed))completionBlock
|
||||
{
|
||||
if (_animationType == RCTAnimationTypeSpring) {
|
||||
|
||||
[UIView animateWithDuration:_duration
|
||||
delay:_delay
|
||||
usingSpringWithDamping:_springDamping
|
||||
initialSpringVelocity:_initialVelocity
|
||||
options:UIViewAnimationOptionBeginFromCurrentState
|
||||
animations:animations
|
||||
completion:completionBlock];
|
||||
|
||||
} else {
|
||||
|
||||
UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState |
|
||||
UIViewAnimationCurveFromRCTAnimationType(_animationType);
|
||||
|
||||
[UIView animateWithDuration:_duration
|
||||
delay:_delay
|
||||
options:options
|
||||
animations:animations
|
||||
completion:completionBlock];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTLayoutAnimation : NSObject
|
||||
|
||||
@property (nonatomic, strong) RCTAnimation *createAnimation;
|
||||
@property (nonatomic, strong) RCTAnimation *updateAnimation;
|
||||
@property (nonatomic, strong) RCTAnimation *deleteAnimation;
|
||||
@property (nonatomic, strong) RCTResponseSenderBlock callback;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTLayoutAnimation
|
||||
|
||||
- (instancetype)initWithDictionary:(NSDictionary *)config callback:(RCTResponseSenderBlock)callback
|
||||
{
|
||||
if (!config) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ((self = [super init])) {
|
||||
|
||||
// TODO: this should be provided in ms, not seconds
|
||||
NSTimeInterval duration = [RCTConvert NSTimeInterval:config[@"duration"]];
|
||||
|
||||
_createAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"create"]];
|
||||
_updateAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"update"]];
|
||||
_deleteAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"delete"]];
|
||||
_callback = callback;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTUIManager
|
||||
{
|
||||
// Root views are only mutated on the shadow queue
|
||||
NSDictionary *_viewManagers;
|
||||
NSMutableSet *_rootViewTags;
|
||||
NSMutableArray *_pendingUIBlocks;
|
||||
NSLock *_pendingUIBlocksLock;
|
||||
|
||||
pthread_mutex_t _pendingUIBlocksMutex;
|
||||
NSDictionary *_nextLayoutAnimationConfig; // RCT thread only
|
||||
RCTResponseSenderBlock _nextLayoutAnimationCallback; // RCT thread only
|
||||
RCTResponseSenderBlock _layoutAnimationCallbackMT; // Main thread only
|
||||
// Animation
|
||||
RCTLayoutAnimation *_nextLayoutAnimation; // RCT thread only
|
||||
RCTLayoutAnimation *_layoutAnimation; // Main thread only
|
||||
|
||||
NSMutableDictionary *_defaultShadowViews;
|
||||
NSMutableDictionary *_defaultViews;
|
||||
// Keyed by moduleName
|
||||
NSMutableDictionary *_defaultShadowViews; // RCT thread only
|
||||
NSMutableDictionary *_defaultViews; // Main thread only
|
||||
NSDictionary *_viewManagers;
|
||||
|
||||
// Keyed by React tag
|
||||
RCTSparseArray *_viewManagerRegistry; // RCT thread only
|
||||
RCTSparseArray *_shadowViewRegistry; // RCT thread only
|
||||
RCTSparseArray *_viewRegistry; // Main thread only
|
||||
|
||||
__weak RCTBridge *_bridge;
|
||||
}
|
||||
|
||||
- (RCTViewManager *)_managerInstanceForViewWithModuleName:(NSString *)moduleName
|
||||
{
|
||||
RCTViewManager *managerInstance = _viewManagers[moduleName];
|
||||
if (managerInstance == nil) {
|
||||
RCTLogWarn(@"No manager class found for view with module name \"%@\"", moduleName);
|
||||
managerInstance = [[RCTViewManager alloc] init];
|
||||
}
|
||||
return managerInstance;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
|
||||
_bridge = bridge;
|
||||
pthread_mutex_init(&_pendingUIBlocksMutex, NULL);
|
||||
_pendingUIBlocksLock = [[NSLock alloc] init];
|
||||
|
||||
// Instantiate view managers
|
||||
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
|
||||
|
@ -116,16 +225,16 @@ static NSDictionary *RCTViewModuleClasses(void)
|
|||
viewManagers[moduleName] = [[moduleClass alloc] initWithEventDispatcher:_bridge.eventDispatcher];
|
||||
}];
|
||||
_viewManagers = viewManagers;
|
||||
_defaultShadowViews = [[NSMutableDictionary alloc] init];
|
||||
_defaultViews = [[NSMutableDictionary alloc] init];
|
||||
|
||||
_viewRegistry = [[RCTSparseArray alloc] init];
|
||||
_viewManagerRegistry = [[RCTSparseArray alloc] init];
|
||||
_shadowViewRegistry = [[RCTSparseArray alloc] init];
|
||||
_viewRegistry = [[RCTSparseArray alloc] init];
|
||||
|
||||
// Internal resources
|
||||
_pendingUIBlocks = [[NSMutableArray alloc] init];
|
||||
_rootViewTags = [[NSMutableSet alloc] init];
|
||||
|
||||
_defaultShadowViews = [[NSMutableDictionary alloc] init];
|
||||
_defaultViews = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
@ -138,7 +247,6 @@ static NSDictionary *RCTViewModuleClasses(void)
|
|||
- (void)dealloc
|
||||
{
|
||||
RCTAssert(!self.valid, @"must call -invalidate before -dealloc");
|
||||
pthread_mutex_destroy(&_pendingUIBlocksMutex);
|
||||
}
|
||||
|
||||
- (BOOL)isValid
|
||||
|
@ -153,9 +261,9 @@ static NSDictionary *RCTViewModuleClasses(void)
|
|||
_viewRegistry = nil;
|
||||
_shadowViewRegistry = nil;
|
||||
|
||||
pthread_mutex_lock(&_pendingUIBlocksMutex);
|
||||
[_pendingUIBlocksLock lock];
|
||||
_pendingUIBlocks = nil;
|
||||
pthread_mutex_unlock(&_pendingUIBlocksMutex);
|
||||
[_pendingUIBlocksLock unlock];
|
||||
}
|
||||
|
||||
- (void)registerRootView:(RCTRootView *)rootView;
|
||||
|
@ -171,6 +279,9 @@ static NSDictionary *RCTViewModuleClasses(void)
|
|||
_viewRegistry[reactTag] = rootView;
|
||||
CGRect frame = rootView.frame;
|
||||
|
||||
// Register manager (TODO: should we do this, or leave it nil?)
|
||||
_viewManagerRegistry[reactTag] = _viewManagers[[RCTViewManager moduleName]];
|
||||
|
||||
// Register shadow view
|
||||
dispatch_async(_bridge.shadowQueue, ^{
|
||||
|
||||
|
@ -192,7 +303,7 @@ static NSDictionary *RCTViewModuleClasses(void)
|
|||
{
|
||||
for (id<RCTViewNodeProtocol> child in children) {
|
||||
RCTTraverseViewNodes(registry[child.reactTag], ^(id<RCTViewNodeProtocol> subview) {
|
||||
RCTAssert(![subview isReactRootView], @"Host views should not be unregistered");
|
||||
RCTAssert(![subview isReactRootView], @"Root views should not be unregistered");
|
||||
if ([subview conformsToProtocol:@protocol(RCTInvalidating)]) {
|
||||
[(id<RCTInvalidating>)subview invalidate];
|
||||
}
|
||||
|
@ -203,8 +314,8 @@ static NSDictionary *RCTViewModuleClasses(void)
|
|||
|
||||
- (void)addUIBlock:(RCTViewManagerUIBlock)block
|
||||
{
|
||||
// This assert is fragile. This is temporary pending t4698600
|
||||
RCTAssert(![NSThread isMainThread], @"This method should only be called on the shadow thread");
|
||||
|
||||
__weak RCTUIManager *weakViewManager = self;
|
||||
__weak RCTSparseArray *weakViewRegistry = _viewRegistry;
|
||||
dispatch_block_t outerBlock = ^{
|
||||
|
@ -215,34 +326,12 @@ static NSDictionary *RCTViewModuleClasses(void)
|
|||
}
|
||||
};
|
||||
|
||||
pthread_mutex_lock(&_pendingUIBlocksMutex);
|
||||
[_pendingUIBlocks addObject:[outerBlock copy]];
|
||||
pthread_mutex_unlock(&_pendingUIBlocksMutex);
|
||||
[_pendingUIBlocksLock lock];
|
||||
[_pendingUIBlocks addObject:outerBlock];
|
||||
[_pendingUIBlocksLock unlock];
|
||||
}
|
||||
|
||||
- (void)setViewLayout:(UIView *)view withAnchorPoint:(CGPoint)anchorPoint position:(CGPoint)position bounds:(CGRect)bounds config:(RCTAnimationConfig *)config completion:(void (^)(BOOL finished))completion
|
||||
{
|
||||
if (isnan(position.x) || isnan(position.y) ||
|
||||
isnan(bounds.origin.x) || isnan(bounds.origin.y) ||
|
||||
isnan(bounds.size.width) || isnan(bounds.size.height)) {
|
||||
RCTLogError(@"Invalid layout for (%zd)%@. position: %@. bounds: %@", [view reactTag], self, NSStringFromCGPoint(position), NSStringFromCGRect(bounds));
|
||||
return;
|
||||
}
|
||||
view.layer.anchorPoint = anchorPoint;
|
||||
view.layer.position = position;
|
||||
view.layer.bounds = bounds;
|
||||
completion(YES);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO: `RCTBridge` has first class knowledge of this method. We should either:
|
||||
* 1. Require that the JS trigger this after a batch - almost like a flush.
|
||||
* 2. Build in support to the `<BatchedExports>` protocol so that each module
|
||||
* may return values to JS via a third callback function passed in, but can
|
||||
* return a tuple that is `(UIThreadBlocks, JSThreadBlocks)`.
|
||||
*/
|
||||
- (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)hostShadowView
|
||||
- (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)rootShadowView
|
||||
{
|
||||
NSMutableSet *viewsWithNewFrames = [NSMutableSet setWithCapacity:1];
|
||||
|
||||
|
@ -250,12 +339,8 @@ static NSDictionary *RCTViewModuleClasses(void)
|
|||
// `frameTags`/`frames` that is created/mutated in the JS thread. We access
|
||||
// these structures in the UI-thread block. `NSMutableArray` is not thread
|
||||
// safe so we rely on the fact that we never mutate it after it's passed to
|
||||
// the main thread. To help protect against mutation, we alias the variable to
|
||||
// a threadsafe `NSArray`, however the `NSArray` doesn't guarantee deep
|
||||
// immutability so we must be very careful.
|
||||
// https://developer.apple.com/library/mac/documentation/Cocoa/
|
||||
// Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html
|
||||
[hostShadowView collectRootUpdatedFrames:viewsWithNewFrames parentConstraint:CGSizeMake(CSS_UNDEFINED, CSS_UNDEFINED)];
|
||||
// the main thread.
|
||||
[rootShadowView collectRootUpdatedFrames:viewsWithNewFrames parentConstraint:(CGSize){CSS_UNDEFINED, CSS_UNDEFINED}];
|
||||
|
||||
// Parallel arrays
|
||||
NSMutableArray *frameReactTags = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
||||
|
@ -269,37 +354,75 @@ static NSDictionary *RCTViewModuleClasses(void)
|
|||
[areNew addObject:@(shadowView.isNewView)];
|
||||
[parentsAreNew addObject:@(shadowView.superview.isNewView)];
|
||||
}
|
||||
|
||||
for (RCTShadowView *shadowView in viewsWithNewFrames) {
|
||||
// We have to do this after we build the parentsAreNew array.
|
||||
shadowView.newView = NO;
|
||||
}
|
||||
|
||||
NSArray *immutableFrameReactTags = frameReactTags;
|
||||
NSArray *immutableFrames = frames;
|
||||
|
||||
NSNumber *rootViewTag = hostShadowView.reactTag;
|
||||
return ^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry) {
|
||||
for (NSUInteger ii = 0; ii < immutableFrames.count; ii++) {
|
||||
NSNumber *reactTag = immutableFrameReactTags[ii];
|
||||
// Perform layout (possibly animated)
|
||||
NSNumber *rootViewTag = rootShadowView.reactTag;
|
||||
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
for (NSUInteger ii = 0; ii < frames.count; ii++) {
|
||||
NSNumber *reactTag = frameReactTags[ii];
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
CGRect frame = [immutableFrames[ii] CGRectValue];
|
||||
CGRect frame = [frames[ii] CGRectValue];
|
||||
|
||||
// These frames are in terms of anchorPoint = topLeft, but internally the
|
||||
// views are anchorPoint = center for easier scale and rotation animations.
|
||||
// Convert the frame so it works with anchorPoint = center.
|
||||
__weak RCTUIManager *weakSelf = self;
|
||||
[self setViewLayout:view
|
||||
withAnchorPoint:CGPointMake(0.5, 0.5)
|
||||
position:CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame))
|
||||
bounds:CGRectMake(0, 0, frame.size.width, frame.size.height)
|
||||
config:/*!isNew ? _layoutAnimationConfigMT.updateConfig : */nil // TODO: !!!
|
||||
completion:^(BOOL finished) {
|
||||
__strong RCTUIManager *strongSelf = weakSelf;
|
||||
if (strongSelf->_layoutAnimationCallbackMT) {
|
||||
strongSelf->_layoutAnimationCallbackMT(@[@(finished)]);
|
||||
CGPoint position = {CGRectGetMidX(frame), CGRectGetMidY(frame)};
|
||||
CGRect bounds = {0, 0, frame.size};
|
||||
|
||||
// Avoid crashes due to nan coords
|
||||
if (isnan(position.x) || isnan(position.y) ||
|
||||
isnan(bounds.origin.x) || isnan(bounds.origin.y) ||
|
||||
isnan(bounds.size.width) || isnan(bounds.size.height)) {
|
||||
RCTLogError(@"Invalid layout for (%zd)%@. position: %@. bounds: %@", [view reactTag], self, NSStringFromCGPoint(position), NSStringFromCGRect(bounds));
|
||||
continue;
|
||||
}
|
||||
}];
|
||||
|
||||
void (^completion)(BOOL finished) = ^(BOOL finished) {
|
||||
if (self->_layoutAnimation.callback) {
|
||||
self->_layoutAnimation.callback(@[@(finished)]);
|
||||
}
|
||||
};
|
||||
|
||||
// Animate view update
|
||||
BOOL isNew = [areNew[ii] boolValue];
|
||||
RCTAnimation *updateAnimation = isNew ? nil: _layoutAnimation.updateAnimation;
|
||||
if (updateAnimation) {
|
||||
[updateAnimation performAnimations:^{
|
||||
view.layer.position = position;
|
||||
view.layer.bounds = bounds;
|
||||
} withCompletionBlock:completion];
|
||||
} else {
|
||||
view.layer.position = position;
|
||||
view.layer.bounds = bounds;
|
||||
completion(YES);
|
||||
}
|
||||
|
||||
// Animate view creations
|
||||
BOOL shouldAnimateCreation = isNew && ![parentsAreNew[ii] boolValue];
|
||||
RCTAnimation *createAnimation = _layoutAnimation.createAnimation;
|
||||
if (shouldAnimateCreation && createAnimation) {
|
||||
if ([createAnimation.property isEqualToString:@"scaleXY"]) {
|
||||
view.layer.transform = CATransform3DMakeScale(0, 0, 0);
|
||||
} else if ([createAnimation.property isEqualToString:@"opacity"]) {
|
||||
view.layer.opacity = 0.0;
|
||||
}
|
||||
[createAnimation performAnimations:^{
|
||||
if ([createAnimation.property isEqual:@"scaleXY"]) {
|
||||
view.layer.transform = CATransform3DIdentity;
|
||||
} else if ([createAnimation.property isEqual:@"opacity"]) {
|
||||
view.layer.opacity = 1.0;
|
||||
} else {
|
||||
RCTLogError(@"Unsupported layout animation createConfig property %@", createAnimation.property);
|
||||
}
|
||||
} withCompletionBlock:nil];
|
||||
}
|
||||
}
|
||||
|
||||
RCTRootView *rootView = _viewRegistry[rootViewTag];
|
||||
RCTTraverseViewNodes(rootView, ^(id<RCTViewNodeProtocol> view) {
|
||||
if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) {
|
||||
|
@ -314,7 +437,7 @@ static NSDictionary *RCTViewModuleClasses(void)
|
|||
NSMutableSet *applierBlocks = [NSMutableSet setWithCapacity:1];
|
||||
[topView collectUpdatedProperties:applierBlocks parentProperties:@{}];
|
||||
|
||||
[self addUIBlock:^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry) {
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
for (RCTApplierBlock block in applierBlocks) {
|
||||
block(viewRegistry);
|
||||
}
|
||||
|
@ -391,10 +514,10 @@ static NSDictionary *RCTViewModuleClasses(void)
|
|||
[self _purgeChildren:@[rootShadowView] fromRegistry:_shadowViewRegistry];
|
||||
[_rootViewTags removeObject:rootReactTag];
|
||||
|
||||
[self addUIBlock:^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry){
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
||||
RCTCAssertMainThread();
|
||||
UIView *rootView = viewRegistry[rootReactTag];
|
||||
[viewManager _purgeChildren:@[rootView] fromRegistry:viewRegistry];
|
||||
[uiManager _purgeChildren:@[rootView] fromRegistry:viewRegistry];
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -437,9 +560,9 @@ static NSDictionary *RCTViewModuleClasses(void)
|
|||
removeAtIndices:removeAtIndices
|
||||
registry:_shadowViewRegistry];
|
||||
|
||||
[self addUIBlock:^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry){
|
||||
RCTCAssertMainThread();
|
||||
[viewManager _manageChildren:containerReactTag
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
||||
|
||||
[uiManager _manageChildren:containerReactTag
|
||||
moveFromIndices:moveFromIndices
|
||||
moveToIndices:moveToIndices
|
||||
addChildReactTags:addChildReactTags
|
||||
|
@ -549,7 +672,11 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
{
|
||||
RCT_EXPORT(createView);
|
||||
|
||||
RCTViewManager *manager = [self _managerInstanceForViewWithModuleName:moduleName];
|
||||
RCTViewManager *manager = _viewManagers[moduleName];
|
||||
if (manager == nil) {
|
||||
RCTLogWarn(@"No manager class found for view with module name \"%@\"", moduleName);
|
||||
manager = [[RCTViewManager alloc] init];
|
||||
}
|
||||
|
||||
// Generate default view, used for resetting default props
|
||||
if (!_defaultShadowViews[moduleName]) {
|
||||
|
@ -565,6 +692,9 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
||||
RCTCAssertMainThread();
|
||||
|
||||
// Register manager (TODO: should we do this, or leave it nil?)
|
||||
uiManager->_viewManagerRegistry[reactTag] = manager;
|
||||
|
||||
// Generate default view, used for resetting default props
|
||||
if (!uiManager->_defaultViews[moduleName]) {
|
||||
// Note the default is setup after the props are read for the first time ever
|
||||
|
@ -575,6 +705,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
|
||||
UIView *view = [manager view];
|
||||
if (view) {
|
||||
|
||||
// Set required properties
|
||||
view.reactTag = reactTag;
|
||||
view.multipleTouchEnabled = YES;
|
||||
|
@ -588,18 +719,20 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
}];
|
||||
}
|
||||
|
||||
- (void)updateView:(NSNumber *)reactTag moduleName:(NSString *)moduleName props:(NSDictionary *)props
|
||||
// TODO: remove moduleName param as it isn't needed
|
||||
- (void)updateView:(NSNumber *)reactTag moduleName:(__unused NSString *)_ props:(NSDictionary *)props
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
RCTViewManager *viewManager = _viewManagerRegistry[reactTag];
|
||||
NSString *moduleName = [[viewManager class] moduleName];
|
||||
|
||||
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
|
||||
RCTViewManager *manager = [self _managerInstanceForViewWithModuleName:moduleName];
|
||||
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[moduleName], manager);
|
||||
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[moduleName], viewManager);
|
||||
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
RCTCAssertMainThread();
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
RCTSetViewProps(props, view, uiManager->_defaultViews[moduleName], manager);
|
||||
UIView *view = uiManager->_viewRegistry[reactTag];
|
||||
RCTSetViewProps(props, view, uiManager->_defaultViews[moduleName], viewManager);
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -608,7 +741,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
RCT_EXPORT(focus);
|
||||
|
||||
if (!reactTag) return;
|
||||
[self addUIBlock:^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry) {
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
UIView *newResponder = viewRegistry[reactTag];
|
||||
[newResponder becomeFirstResponder];
|
||||
}];
|
||||
|
@ -619,7 +752,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
RCT_EXPORT(blur);
|
||||
|
||||
if (!reactTag) return;
|
||||
[self addUIBlock:^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry){
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
||||
UIView *currentResponder = viewRegistry[reactTag];
|
||||
[currentResponder resignFirstResponder];
|
||||
}];
|
||||
|
@ -630,24 +763,40 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
// First copy the previous blocks into a temporary variable, then reset the
|
||||
// pending blocks to a new array. This guards against mutation while
|
||||
// processing the pending blocks in another thread.
|
||||
|
||||
for (RCTViewManager *manager in _viewManagers.allValues) {
|
||||
RCTViewManagerUIBlock uiBlock = [manager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
|
||||
if (uiBlock != nil) {
|
||||
if (uiBlock) {
|
||||
[self addUIBlock:uiBlock];
|
||||
}
|
||||
}
|
||||
|
||||
// Set up next layout animation
|
||||
if (_nextLayoutAnimation) {
|
||||
RCTLayoutAnimation *layoutAnimation = _nextLayoutAnimation;
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
uiManager->_layoutAnimation = layoutAnimation;
|
||||
}];
|
||||
}
|
||||
|
||||
// Perform layout
|
||||
for (NSNumber *reactTag in _rootViewTags) {
|
||||
RCTShadowView *rootView = _shadowViewRegistry[reactTag];
|
||||
[self addUIBlock:[self uiBlockWithLayoutUpdateForRootView:rootView]];
|
||||
[self _amendPendingUIBlocksWithStylePropagationUpdateForRootView:rootView];
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&_pendingUIBlocksMutex);
|
||||
// Clear layout animations
|
||||
if (_nextLayoutAnimation) {
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
uiManager->_layoutAnimation = nil;
|
||||
}];
|
||||
_nextLayoutAnimation = nil;
|
||||
}
|
||||
|
||||
[_pendingUIBlocksLock lock];
|
||||
NSArray *previousPendingUIBlocks = _pendingUIBlocks;
|
||||
_pendingUIBlocks = [[NSMutableArray alloc] init];
|
||||
pthread_mutex_unlock(&_pendingUIBlocksMutex);
|
||||
[_pendingUIBlocksLock unlock];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
for (dispatch_block_t block in previousPendingUIBlocks) {
|
||||
|
@ -656,16 +805,6 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
});
|
||||
}
|
||||
|
||||
- (void)layoutRootShadowView:(RCTShadowView *)rootShadowView
|
||||
{
|
||||
RCTViewManagerUIBlock uiBlock = [self uiBlockWithLayoutUpdateForRootView:rootShadowView];
|
||||
__weak RCTUIManager *weakViewManager = self;
|
||||
__weak RCTSparseArray *weakViewRegistry = _viewRegistry;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
uiBlock(weakViewManager, weakViewRegistry);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)measure:(NSNumber *)reactTag callback:(RCTResponseSenderBlock)callback
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
@ -675,7 +814,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
return;
|
||||
}
|
||||
|
||||
[self addUIBlock:^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry) {
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
if (!view) {
|
||||
RCTLogError(@"measure cannot find view with tag %zd", reactTag);
|
||||
|
@ -705,7 +844,6 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
}];
|
||||
}
|
||||
|
||||
|
||||
- (void)requestSchedulingJavaScriptNavigation:(NSNumber *)reactTag
|
||||
errorCallback:(RCTResponseSenderBlock)errorCallback
|
||||
callback:(RCTResponseSenderBlock)callback
|
||||
|
@ -716,7 +854,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
RCTLogError(@"Callback not provided for navigation scheduling.");
|
||||
return;
|
||||
}
|
||||
[self addUIBlock:^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry){
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
||||
if (reactTag) {
|
||||
//TODO: This is nasty - why is RCTNavigator hard-coded?
|
||||
id rkObject = viewRegistry[reactTag];
|
||||
|
@ -870,24 +1008,24 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
[self addUIBlock:^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry){
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
||||
// - There should be at most one designated "main scroll view"
|
||||
// - There should be at most one designated "`nativeMainScrollDelegate`"
|
||||
// - The one designated main scroll view should have the one designated
|
||||
// `nativeMainScrollDelegate` set as its `nativeMainScrollDelegate`.
|
||||
if (viewManager.mainScrollView) {
|
||||
viewManager.mainScrollView.nativeMainScrollDelegate = nil;
|
||||
if (uiManager.mainScrollView) {
|
||||
uiManager.mainScrollView.nativeMainScrollDelegate = nil;
|
||||
}
|
||||
if (reactTag) {
|
||||
id rkObject = viewRegistry[reactTag];
|
||||
if ([rkObject conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
||||
viewManager.mainScrollView = (id<RCTScrollableProtocol>)rkObject;
|
||||
((id<RCTScrollableProtocol>)rkObject).nativeMainScrollDelegate = viewManager.nativeMainScrollDelegate;
|
||||
uiManager.mainScrollView = (id<RCTScrollableProtocol>)rkObject;
|
||||
((id<RCTScrollableProtocol>)rkObject).nativeMainScrollDelegate = uiManager.nativeMainScrollDelegate;
|
||||
} else {
|
||||
RCTCAssert(NO, @"Tag %@ does not conform to RCTScrollableProtocol", reactTag);
|
||||
}
|
||||
} else {
|
||||
viewManager.mainScrollView = nil;
|
||||
uiManager.mainScrollView = nil;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
@ -896,7 +1034,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
{
|
||||
RCT_EXPORT(scrollTo);
|
||||
|
||||
[self addUIBlock:^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry){
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
||||
[(id<RCTScrollableProtocol>)view scrollToOffset:CGPointMake([offsetX floatValue], [offsetY floatValue])];
|
||||
|
@ -910,7 +1048,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
{
|
||||
RCT_EXPORT(zoomToRect);
|
||||
|
||||
[self addUIBlock:^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry){
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
||||
[(id<RCTScrollableProtocol>)view zoomToRect:[RCTConvert CGRect:rectDict] animated:YES];
|
||||
|
@ -924,7 +1062,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
[self addUIBlock:^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry) {
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
if (!view) {
|
||||
NSString *error = [[NSString alloc] initWithFormat:@"cannot find view with tag %@", reactTag];
|
||||
|
@ -947,7 +1085,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
[self addUIBlock:^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry) {
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
_jsResponder = viewRegistry[reactTag];
|
||||
if (!_jsResponder) {
|
||||
RCTLogMustFix(@"Invalid view set to be the JS responder - tag %zd", reactTag);
|
||||
|
@ -959,7 +1097,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
[self addUIBlock:^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry) {
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
_jsResponder = nil;
|
||||
}];
|
||||
}
|
||||
|
@ -1189,18 +1327,19 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
return allJSConstants;
|
||||
}
|
||||
|
||||
- (void)configureNextLayoutAnimation:(NSDictionary *)config withCallback:(RCTResponseSenderBlock)callback errorCallback:(RCTResponseSenderBlock)errorCallback
|
||||
- (void)configureNextLayoutAnimation:(NSDictionary *)config
|
||||
withCallback:(RCTResponseSenderBlock)callback
|
||||
errorCallback:(RCTResponseSenderBlock)errorCallback
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
if (_nextLayoutAnimationCallback || _nextLayoutAnimationConfig) {
|
||||
RCTLogWarn(@"Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@.", _nextLayoutAnimationConfig, config);
|
||||
if (_nextLayoutAnimation) {
|
||||
RCTLogWarn(@"Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@.", _nextLayoutAnimation, config);
|
||||
}
|
||||
if (config[@"delete"] != nil) {
|
||||
RCTLogError(@"LayoutAnimation only supports create and update right now. Config: %@", config);
|
||||
}
|
||||
_nextLayoutAnimationConfig = config;
|
||||
_nextLayoutAnimationCallback = callback;
|
||||
_nextLayoutAnimation = [[RCTLayoutAnimation alloc] initWithDictionary:config callback:callback];
|
||||
}
|
||||
|
||||
static UIView *_jsResponder;
|
||||
|
|
|
@ -124,6 +124,7 @@
|
|||
13B080241A694A8400A75B9A /* RCTWrapperViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWrapperViewController.m; sourceTree = "<group>"; };
|
||||
13B080271A694C4900A75B9A /* RCTDataManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDataManager.h; sourceTree = "<group>"; };
|
||||
13B080281A694C4900A75B9A /* RCTDataManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDataManager.m; sourceTree = "<group>"; };
|
||||
13DB9D681A8CC58200429C20 /* RCTAnimationType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationType.h; 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>"; };
|
||||
|
@ -330,6 +331,7 @@
|
|||
137029521A69923600575408 /* RCTImageDownloader.m */,
|
||||
13B07FCD1A683B5F00A75B9A /* RCTScrollableProtocol.h */,
|
||||
13ED13891A80C9D40050A8F9 /* RCTPointerEvents.h */,
|
||||
13DB9D681A8CC58200429C20 /* RCTAnimationType.h */,
|
||||
);
|
||||
path = Base;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
@ -14,7 +14,7 @@ typedef NS_ENUM(NSUInteger, RCTLayoutLifecycle) {
|
|||
RCTLayoutLifecycleDirtied,
|
||||
};
|
||||
|
||||
// TODO: is this redundact now?
|
||||
// TODO: is this still needed?
|
||||
typedef NS_ENUM(NSUInteger, RCTPropagationLifecycle) {
|
||||
RCTPropagationLifecycleUninitialized = 0,
|
||||
RCTPropagationLifecycleComputed,
|
||||
|
@ -72,7 +72,8 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *);
|
|||
@property (nonatomic, assign) CGFloat height;
|
||||
@property (nonatomic, assign) CGRect frame;
|
||||
|
||||
- (void)updateShadowViewLayout;
|
||||
- (void)setTopLeft:(CGPoint)topLeft;
|
||||
- (void)setSize:(CGSize)size;
|
||||
|
||||
/**
|
||||
* Border. Defaults to 0.
|
||||
|
@ -110,12 +111,22 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *);
|
|||
|
||||
// The following are implementation details exposed to subclasses. Do not call them directly
|
||||
- (void)dirtyLayout;
|
||||
- (void)dirtyPropagation;
|
||||
- (BOOL)isLayoutDirty;
|
||||
|
||||
// TODO: is this still needed?
|
||||
- (void)dirtyPropagation;
|
||||
- (BOOL)isPropagationDirty;
|
||||
|
||||
// TODO: move this to text node?
|
||||
- (void)dirtyText;
|
||||
- (BOOL)isTextDirty;
|
||||
- (void)setTextComputed;
|
||||
|
||||
/**
|
||||
* Triggers a recalculation of the shadow view's layout.
|
||||
*/
|
||||
- (void)updateShadowViewLayout;
|
||||
|
||||
/**
|
||||
* Computes the recursive offset, meaning the sum of all descendant offsets -
|
||||
* this is the sum of all positions inset from parents. This is not merely the
|
||||
|
|
|
@ -103,7 +103,7 @@ static css_node_t *RCTGetChild(void *context, int i)
|
|||
static bool RCTIsDirty(void *context)
|
||||
{
|
||||
RCTShadowView *shadowView = (__bridge RCTShadowView *)context;
|
||||
return shadowView.layoutLifecycle != RCTLayoutLifecycleComputed;
|
||||
return [shadowView isLayoutDirty];
|
||||
}
|
||||
|
||||
// Enforces precedence rules, e.g. marginLeft > marginHorizontal > margin.
|
||||
|
@ -325,6 +325,11 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
|
|||
}
|
||||
}
|
||||
|
||||
- (BOOL)isLayoutDirty
|
||||
{
|
||||
return _layoutLifecycle != RCTLayoutLifecycleComputed;
|
||||
}
|
||||
|
||||
- (void)dirtyPropagation
|
||||
{
|
||||
if (_propagationLifecycle != RCTPropagationLifecycleDirtied) {
|
||||
|
@ -333,6 +338,11 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
|
|||
}
|
||||
}
|
||||
|
||||
- (BOOL)isPropagationDirty
|
||||
{
|
||||
return _propagationLifecycle != RCTLayoutLifecycleComputed;
|
||||
}
|
||||
|
||||
- (void)dirtyText
|
||||
{
|
||||
if (_textLifecycle != RCTTextLifecycleDirtied) {
|
||||
|
@ -391,23 +401,6 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
|
|||
return self.reactTag;
|
||||
}
|
||||
|
||||
- (void)updateShadowViewLayout
|
||||
{
|
||||
if (_recomputePadding) {
|
||||
RCTProcessMetaProps(_paddingMetaProps, _cssNode->style.padding);
|
||||
}
|
||||
if (_recomputeMargin) {
|
||||
RCTProcessMetaProps(_marginMetaProps, _cssNode->style.margin);
|
||||
}
|
||||
if (_recomputePadding || _recomputeMargin) {
|
||||
[self dirtyLayout];
|
||||
}
|
||||
[self fillCSSNode:_cssNode];
|
||||
_recomputeMargin = NO;
|
||||
_recomputePadding = NO;
|
||||
}
|
||||
|
||||
|
||||
// Margin
|
||||
|
||||
#define RCT_MARGIN_PROPERTY(prop, metaProp) \
|
||||
|
@ -503,6 +496,20 @@ RCT_POSITION_PROPERTY(Left, left, LEFT)
|
|||
[self dirtyLayout];
|
||||
}
|
||||
|
||||
- (void)setTopLeft:(CGPoint)topLeft
|
||||
{
|
||||
_cssNode->style.position[CSS_LEFT] = topLeft.x;
|
||||
_cssNode->style.position[CSS_TOP] = topLeft.y;
|
||||
[self dirtyLayout];
|
||||
}
|
||||
|
||||
- (void)setSize:(CGSize)size
|
||||
{
|
||||
_cssNode->style.dimensions[CSS_WIDTH] = size.width;
|
||||
_cssNode->style.dimensions[CSS_HEIGHT] = size.height;
|
||||
[self dirtyLayout];
|
||||
}
|
||||
|
||||
// Flex
|
||||
|
||||
#define RCT_STYLE_PROPERTY(setProp, getProp, cssProp, type) \
|
||||
|
@ -530,4 +537,20 @@ RCT_STYLE_PROPERTY(FlexWrap, flexWrap, flex_wrap, css_wrap_type_t)
|
|||
[self dirtyPropagation];
|
||||
}
|
||||
|
||||
- (void)updateShadowViewLayout
|
||||
{
|
||||
if (_recomputePadding) {
|
||||
RCTProcessMetaProps(_paddingMetaProps, _cssNode->style.padding);
|
||||
}
|
||||
if (_recomputeMargin) {
|
||||
RCTProcessMetaProps(_marginMetaProps, _cssNode->style.margin);
|
||||
}
|
||||
if (_recomputePadding || _recomputeMargin) {
|
||||
[self dirtyLayout];
|
||||
}
|
||||
[self fillCSSNode:_cssNode];
|
||||
_recomputeMargin = NO;
|
||||
_recomputePadding = NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -80,12 +80,12 @@ RCT_REMAP_VIEW_PROPERTY(containerBackgroundColor, backgroundColor)
|
|||
// layout to copy its properties across?
|
||||
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry
|
||||
{
|
||||
NSMutableArray *shadowBlocks = [NSMutableArray new];
|
||||
NSMutableArray *uiBlocks = [NSMutableArray new];
|
||||
|
||||
// TODO: are modules global, or specific to a given rootView?
|
||||
for (RCTShadowView *rootView in shadowViewRegistry.allObjects) {
|
||||
if (![rootView isReactRootView]) {
|
||||
// This isn't a host view
|
||||
// This isn't a root view
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@ RCT_REMAP_VIEW_PROPERTY(containerBackgroundColor, backgroundColor)
|
|||
[shadowView setTextComputed];
|
||||
}
|
||||
|
||||
[shadowBlocks addObject:^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry) {
|
||||
[uiBlocks addObject:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
[reactTaggedAttributedStrings enumerateObjectsUsingBlock:^(NSAttributedString *attributedString, NSNumber *reactTag, BOOL *stop) {
|
||||
RCTText *text = viewRegistry[reactTag];
|
||||
text.attributedText = attributedString;
|
||||
|
@ -125,9 +125,9 @@ RCT_REMAP_VIEW_PROPERTY(containerBackgroundColor, backgroundColor)
|
|||
}];
|
||||
}
|
||||
|
||||
return ^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry) {
|
||||
for (RCTViewManagerUIBlock shadowBlock in shadowBlocks) {
|
||||
shadowBlock(viewManager, viewRegistry);
|
||||
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
for (RCTViewManagerUIBlock shadowBlock in uiBlocks) {
|
||||
shadowBlock(uiManager, viewRegistry);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,10 +5,6 @@ var FileWatcher = require('../../FileWatcher');
|
|||
var DependencyGraph = require('./DependencyGraph');
|
||||
var ModuleDescriptor = require('../ModuleDescriptor');
|
||||
|
||||
var DEFAULT_POLYFILLS = [
|
||||
|
||||
];
|
||||
|
||||
var DEFINE_MODULE_CODE =
|
||||
'__d(' +
|
||||
'\'_moduleName_\',' +
|
||||
|
@ -39,6 +35,8 @@ function HasteDependencyResolver(config) {
|
|||
: path.join(__dirname, 'polyfills/prelude.js'),
|
||||
path.join(__dirname, 'polyfills/require.js'),
|
||||
path.join(__dirname, 'polyfills/polyfills.js'),
|
||||
path.join(__dirname, 'polyfills/console.js'),
|
||||
path.join(__dirname, 'polyfills/error-guard.js'),
|
||||
].concat(
|
||||
config.polyfillModuleNames || []
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue