Updates from Fri 5 Jun

This commit is contained in:
Eric Vicenti 2015-06-05 15:11:57 -07:00
commit 0293def7a9
67 changed files with 1761 additions and 658 deletions

View File

@ -281,7 +281,7 @@ var SearchScreen = React.createClass({
renderRow={this.renderRow} renderRow={this.renderRow}
onEndReached={this.onEndReached} onEndReached={this.onEndReached}
automaticallyAdjustContentInsets={false} automaticallyAdjustContentInsets={false}
keyboardDismissMode="onDrag" keyboardDismissMode="on-drag"
keyboardShouldPersistTaps={true} keyboardShouldPersistTaps={true}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
/>; />;

View File

@ -0,0 +1,83 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
'use strict';
var React = require('react-native');
var {
ProgressViewIOS,
StyleSheet,
View,
} = React;
var TimerMixin = require('react-timer-mixin');
var ProgressViewExample = React.createClass({
mixins: [TimerMixin],
getInitialState() {
return {
progress: 0,
};
},
componentDidMount() {
this.updateProgress();
},
updateProgress() {
var progress = this.state.progress + 0.01;
this.setState({ progress });
this.requestAnimationFrame(() => this.updateProgress());
},
getProgress(offset) {
var progress = this.state.progress + offset;
return Math.sin(progress % Math.PI) % 1;
},
render() {
return (
<View style={styles.container}>
<ProgressViewIOS style={styles.progressView} progress={this.getProgress(0)}/>
<ProgressViewIOS style={styles.progressView} progressTintColor="purple" progress={this.getProgress(0.2)}/>
<ProgressViewIOS style={styles.progressView} progressTintColor="red" progress={this.getProgress(0.4)}/>
<ProgressViewIOS style={styles.progressView} progressTintColor="orange" progress={this.getProgress(0.6)}/>
<ProgressViewIOS style={styles.progressView} progressTintColor="yellow" progress={this.getProgress(0.8)}/>
</View>
);
},
});
exports.framework = 'React';
exports.title = 'ProgressViewIOS';
exports.description = 'ProgressViewIOS';
exports.examples = [{
title: 'ProgressViewIOS',
render() {
return (
<ProgressViewExample/>
);
}
}];
var styles = StyleSheet.create({
container: {
marginTop: -20,
backgroundColor: 'transparent',
},
progressView: {
marginTop: 20,
}
});

View File

@ -32,11 +32,11 @@ exports.examples = [{
render() { render() {
return ( return (
<View> <View>
{Object.keys(StatusBarIOS.Style).map((key) => {['default', 'light-content'].map((style) =>
<TouchableHighlight style={styles.wrapper} <TouchableHighlight style={styles.wrapper}
onPress={() => StatusBarIOS.setStyle(StatusBarIOS.Style[key])}> onPress={() => StatusBarIOS.setStyle(style)}>
<View style={styles.button}> <View style={styles.button}>
<Text>setStyle(StatusBarIOS.Style.{key})</Text> <Text>setStyle('{style}')</Text>
</View> </View>
</TouchableHighlight> </TouchableHighlight>
)} )}
@ -48,11 +48,11 @@ exports.examples = [{
render() { render() {
return ( return (
<View> <View>
{Object.keys(StatusBarIOS.Style).map((key) => {['default', 'light-content'].map((style) =>
<TouchableHighlight style={styles.wrapper} <TouchableHighlight style={styles.wrapper}
onPress={() => StatusBarIOS.setStyle(StatusBarIOS.Style[key], true)}> onPress={() => StatusBarIOS.setStyle(style, true)}>
<View style={styles.button}> <View style={styles.button}>
<Text>setStyle(StatusBarIOS.Style.{key}, true)</Text> <Text>setStyle('{style}', true)</Text>
</View> </View>
</TouchableHighlight> </TouchableHighlight>
)} )}
@ -64,18 +64,18 @@ exports.examples = [{
render() { render() {
return ( return (
<View> <View>
{Object.keys(StatusBarIOS.Animation).map((key) => {['none', 'fade', 'slide'].map((animation) =>
<View> <View>
<TouchableHighlight style={styles.wrapper} <TouchableHighlight style={styles.wrapper}
onPress={() => StatusBarIOS.setHidden(true, StatusBarIOS.Animation[key])}> onPress={() => StatusBarIOS.setHidden(true, animation)}>
<View style={styles.button}> <View style={styles.button}>
<Text>setHidden(true, StatusBarIOS.Animation.{key})</Text> <Text>setHidden(true, '{animation}')</Text>
</View> </View>
</TouchableHighlight> </TouchableHighlight>
<TouchableHighlight style={styles.wrapper} <TouchableHighlight style={styles.wrapper}
onPress={() => StatusBarIOS.setHidden(false, StatusBarIOS.Animation[key])}> onPress={() => StatusBarIOS.setHidden(false, animation)}>
<View style={styles.button}> <View style={styles.button}>
<Text>setHidden(false, StatusBarIOS.Animation.{key})</Text> <Text>setHidden(false, '{animation}')</Text>
</View> </View>
</TouchableHighlight> </TouchableHighlight>
</View> </View>

View File

@ -75,6 +75,14 @@ exports.examples = [
render: function(): ReactElement { render: function(): ReactElement {
return <TouchableFeedbackEvents />; return <TouchableFeedbackEvents />;
}, },
}, {
title: 'Touchable delay for events',
description: '<Touchable*> components also accept delayPressIn, ' +
'delayPressOut, and delayLongPress as props. These props impact the ' +
'timing of feedback events.',
render: function(): ReactElement {
return <TouchableDelayEvents />;
},
}]; }];
var TextOnPressBox = React.createClass({ var TextOnPressBox = React.createClass({
@ -148,6 +156,44 @@ var TouchableFeedbackEvents = React.createClass({
}, },
}); });
var TouchableDelayEvents = React.createClass({
getInitialState: function() {
return {
eventLog: [],
};
},
render: function() {
return (
<View>
<View style={[styles.row, {justifyContent: 'center'}]}>
<TouchableOpacity
style={styles.wrapper}
onPress={() => this._appendEvent('press')}
delayPressIn={400}
onPressIn={() => this._appendEvent('pressIn - 400ms delay')}
delayPressOut={1000}
onPressOut={() => this._appendEvent('pressOut - 1000ms delay')}
delayLongPress={800}
onLongPress={() => this._appendEvent('longPress - 800ms delay')}>
<Text style={styles.button}>
Press Me
</Text>
</TouchableOpacity>
</View>
<View style={styles.eventLogBox}>
{this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)}
</View>
</View>
);
},
_appendEvent: function(eventName) {
var limit = 6;
var eventLog = this.state.eventLog.slice(0, limit - 1);
eventLog.unshift(eventName);
this.setState({eventLog});
},
});
var heartImage = {uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small'}; var heartImage = {uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small'};
var styles = StyleSheet.create({ var styles = StyleSheet.create({

View File

@ -50,6 +50,10 @@
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#if RUNNING_ON_CI
jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"UIExplorerApp" moduleName:@"UIExplorerApp"
launchOptions:launchOptions]; launchOptions:launchOptions];

View File

@ -7,7 +7,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.facebook.$(PRODUCT_NAME:rfc1034identifier)</string> <string>com.facebook.internal.uiexplorer.local</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
@ -22,6 +22,8 @@
<string>1</string> <string>1</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSLocationWhenInUseUsageDescription</key>
<string>You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*!</string>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key> <key>UIRequiredDeviceCapabilities</key>
@ -34,8 +36,6 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>NSLocationWhenInUseUsageDescription</key>
<string>You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*!</string>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
</dict> </dict>

View File

@ -45,6 +45,7 @@ var COMPONENTS = [
require('./NavigatorIOSColorsExample'), require('./NavigatorIOSColorsExample'),
require('./NavigatorIOSExample'), require('./NavigatorIOSExample'),
require('./PickerIOSExample'), require('./PickerIOSExample'),
require('./ProgressViewIOSExample'),
require('./ScrollViewExample'), require('./ScrollViewExample'),
require('./SegmentedControlIOSExample'), require('./SegmentedControlIOSExample'),
require('./SliderIOSExample'), require('./SliderIOSExample'),
@ -156,7 +157,7 @@ class UIExplorerList extends React.Component {
renderSectionHeader={this._renderSectionHeader} renderSectionHeader={this._renderSectionHeader}
keyboardShouldPersistTaps={true} keyboardShouldPersistTaps={true}
automaticallyAdjustContentInsets={false} automaticallyAdjustContentInsets={false}
keyboardDismissMode="onDrag" keyboardDismissMode="on-drag"
/> />
</View> </View>
); );

View File

@ -16,12 +16,19 @@ var {
View, View,
} = React; } = React;
var deepDiffer = require('deepDiffer');
var DEBUG = false; var DEBUG = false;
var KEY_1 = 'key_1'; var KEY_1 = 'key_1';
var VAL_1 = 'val_1'; var VAL_1 = 'val_1';
var KEY_2 = 'key_2'; var KEY_2 = 'key_2';
var VAL_2 = 'val_2'; var VAL_2 = 'val_2';
var KEY_MERGE = 'key_merge';
var VAL_MERGE_1 = {'foo': 1, 'bar': {'hoo': 1, 'boo': 1}, 'moo': {'a': 3}};
var VAL_MERGE_2 = {'bar': {'hoo': 2}, 'baz': 2, 'moo': {'a': 3}};
var VAL_MERGE_EXPECT =
{'foo': 1, 'bar': {'hoo': 2, 'boo': 1}, 'baz': 2, 'moo': {'a': 3}};
// setup in componentDidMount // setup in componentDidMount
var done; var done;
@ -40,8 +47,9 @@ function expectTrue(condition, message) {
function expectEqual(lhs, rhs, testname) { function expectEqual(lhs, rhs, testname) {
expectTrue( expectTrue(
lhs === rhs, !deepDiffer(lhs, rhs),
'Error in test ' + testname + ': expected ' + rhs + ', got ' + lhs 'Error in test ' + testname + ': expected\n' + JSON.stringify(rhs) +
'\ngot\n' + JSON.stringify(lhs)
); );
} }
@ -93,31 +101,46 @@ function testRemoveItem() {
'Missing KEY_1 or KEY_2 in ' + '(' + result + ')' 'Missing KEY_1 or KEY_2 in ' + '(' + result + ')'
); );
updateMessage('testRemoveItem - add two items'); updateMessage('testRemoveItem - add two items');
AsyncStorage.removeItem(KEY_1, (err) => { AsyncStorage.removeItem(KEY_1, (err2) => {
expectAsyncNoError(err); expectAsyncNoError(err2);
updateMessage('delete successful '); updateMessage('delete successful ');
AsyncStorage.getItem(KEY_1, (err, result) => { AsyncStorage.getItem(KEY_1, (err3, result2) => {
expectAsyncNoError(err); expectAsyncNoError(err3);
expectEqual( expectEqual(
result, result2,
null, null,
'testRemoveItem: key_1 present after delete' 'testRemoveItem: key_1 present after delete'
); );
updateMessage('key properly removed '); updateMessage('key properly removed ');
AsyncStorage.getAllKeys((err, result2) => { AsyncStorage.getAllKeys((err4, result3) => {
expectAsyncNoError(err); expectAsyncNoError(err4);
expectTrue( expectTrue(
result2.indexOf(KEY_1) === -1, result3.indexOf(KEY_1) === -1,
'Unexpected: KEY_1 present in ' + result2 'Unexpected: KEY_1 present in ' + result3
); );
updateMessage('proper length returned.\nDone!'); updateMessage('proper length returned.');
runTestCase('should merge values', testMerge);
});
});
});
});
});
});
}
function testMerge() {
AsyncStorage.setItem(KEY_MERGE, JSON.stringify(VAL_MERGE_1), (err1) => {
expectAsyncNoError(err1);
AsyncStorage.mergeItem(KEY_MERGE, JSON.stringify(VAL_MERGE_2), (err2) => {
expectAsyncNoError(err2);
AsyncStorage.getItem(KEY_MERGE, (err3, result) => {
expectAsyncNoError(err3);
expectEqual(JSON.parse(result), VAL_MERGE_EXPECT, 'testMerge');
updateMessage('objects deeply merged\nDone!');
done(); done();
}); });
}); });
}); });
});
});
});
} }
var AsyncStorageTest = React.createClass({ var AsyncStorageTest = React.createClass({

View File

@ -7,7 +7,6 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
* *
* @providesModule POPAnimation * @providesModule POPAnimation
* @flow
*/ */
'use strict'; 'use strict';
@ -17,7 +16,7 @@ if (!RCTPOPAnimationManager) {
// workaround to enable its availability to be determined at runtime. // workaround to enable its availability to be determined at runtime.
// For Flow let's pretend like we always export POPAnimation // For Flow let's pretend like we always export POPAnimation
// so all our users don't need to do null checks // so all our users don't need to do null checks
module.exports = ((null: any): typeof POPAnimation); module.exports = null;
} else { } else {
var ReactPropTypes = require('ReactPropTypes'); var ReactPropTypes = require('ReactPropTypes');

View File

@ -120,7 +120,7 @@ var DatePickerIOS = React.createClass({
<View style={props.style}> <View style={props.style}>
<RCTDatePickerIOS <RCTDatePickerIOS
ref={DATEPICKER} ref={DATEPICKER}
style={styles.rkDatePickerIOS} style={styles.datePickerIOS}
date={props.date.getTime()} date={props.date.getTime()}
maximumDate={ maximumDate={
props.maximumDate ? props.maximumDate.getTime() : undefined props.maximumDate ? props.maximumDate.getTime() : undefined
@ -128,7 +128,7 @@ var DatePickerIOS = React.createClass({
minimumDate={ minimumDate={
props.minimumDate ? props.minimumDate.getTime() : undefined props.minimumDate ? props.minimumDate.getTime() : undefined
} }
mode={RCTDatePickerIOSConsts.DatePickerModes[props.mode]} mode={props.mode}
minuteInterval={props.minuteInterval} minuteInterval={props.minuteInterval}
timeZoneOffsetInMinutes={props.timeZoneOffsetInMinutes} timeZoneOffsetInMinutes={props.timeZoneOffsetInMinutes}
onChange={this._onChange} onChange={this._onChange}
@ -139,7 +139,7 @@ var DatePickerIOS = React.createClass({
}); });
var styles = StyleSheet.create({ var styles = StyleSheet.create({
rkDatePickerIOS: { datePickerIOS: {
height: RCTDatePickerIOSConsts.ComponentHeight, height: RCTDatePickerIOSConsts.ComponentHeight,
width: RCTDatePickerIOSConsts.ComponentWidth, width: RCTDatePickerIOSConsts.ComponentWidth,
}, },

View File

@ -0,0 +1,49 @@
/**
* 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.
*
* @providesModule ProgressViewIOS
*/
'use strict';
var React = require('React');
var StyleSheet = require('StyleSheet');
var Text = require('Text');
var View = require('View');
var DummyProgressViewIOS = React.createClass({
render: function() {
return (
<View style={[styles.dummy, this.props.style]}>
<Text style={styles.text}>
ProgressViewIOS is not supported on this platform!
</Text>
</View>
);
},
});
var styles = StyleSheet.create({
dummy: {
width: 120,
height: 20,
backgroundColor: '#ffbcbc',
borderWidth: 1,
borderColor: 'red',
alignItems: 'center',
justifyContent: 'center',
},
text: {
color: '#333333',
margin: 5,
fontSize: 10,
}
});
module.exports = DummyProgressViewIOS;

View File

@ -0,0 +1,83 @@
/**
* 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.
*
* @providesModule ProgressViewIOS
* @flow
*/
'use strict';
var Image = require('Image');
var NativeMethodsMixin = require('NativeMethodsMixin');
var NativeModules = require('NativeModules');
var PropTypes = require('ReactPropTypes');
var React = require('React');
var StyleSheet = require('StyleSheet');
var requireNativeComponent = require('requireNativeComponent');
var verifyPropTypes = require('verifyPropTypes');
/**
* Use `ProgressViewIOS` to render a UIProgressView on iOS.
*/
var ProgressViewIOS = React.createClass({
mixins: [NativeMethodsMixin],
propTypes: {
/**
* The progress bar style.
*/
progressViewStyle: PropTypes.oneOf(['default', 'bar']),
/**
* The progress value (between 0 and 1).
*/
progress: PropTypes.number,
/**
* The tint color of the progress bar itself.
*/
progressTintColor: PropTypes.string,
/**
* The tint color of the progress bar track.
*/
trackTintColor: PropTypes.string,
/**
* A stretchable image to display as the progress bar.
*/
progressImage: Image.propTypes.source,
/**
* A stretchable image to display behind the progress bar.
*/
trackImage: Image.propTypes.source,
},
render: function() {
return (
<RCTProgressView
{...this.props}
style={[styles.progressView, this.props.style]}
/>
);
}
});
var styles = StyleSheet.create({
progressView: {
height: NativeModules.ProgressViewManager.ComponentHeight
},
});
var RCTProgressView = requireNativeComponent(
'RCTProgressView',
ProgressViewIOS
);
module.exports = ProgressViewIOS;

View File

@ -38,12 +38,6 @@ var PropTypes = React.PropTypes;
var SCROLLVIEW = 'ScrollView'; var SCROLLVIEW = 'ScrollView';
var INNERVIEW = 'InnerScrollView'; var INNERVIEW = 'InnerScrollView';
var keyboardDismissModeConstants = {
'none': RCTScrollViewConsts.KeyboardDismissMode.None, // default
'interactive': RCTScrollViewConsts.KeyboardDismissMode.Interactive,
'onDrag': RCTScrollViewConsts.KeyboardDismissMode.OnDrag,
};
/** /**
* Component that wraps platform ScrollView while providing * Component that wraps platform ScrollView while providing
* integration with touch locking "responder" system. * integration with touch locking "responder" system.
@ -147,7 +141,7 @@ var ScrollView = React.createClass({
keyboardDismissMode: PropTypes.oneOf([ keyboardDismissMode: PropTypes.oneOf([
'none', // default 'none', // default
'interactive', 'interactive',
'onDrag', 'on-drag',
]), ]),
/** /**
* When false, tapping outside of the focused text input when the keyboard * When false, tapping outside of the focused text input when the keyboard
@ -287,9 +281,6 @@ var ScrollView = React.createClass({
...this.props, ...this.props,
alwaysBounceHorizontal, alwaysBounceHorizontal,
alwaysBounceVertical, alwaysBounceVertical,
keyboardDismissMode: this.props.keyboardDismissMode ?
keyboardDismissModeConstants[this.props.keyboardDismissMode] :
undefined,
style: ([styles.base, this.props.style]: ?Array<any>), style: ([styles.base, this.props.style]: ?Array<any>),
onTouchStart: this.scrollResponderHandleTouchStart, onTouchStart: this.scrollResponderHandleTouchStart,
onTouchMove: this.scrollResponderHandleTouchMove, onTouchMove: this.scrollResponderHandleTouchMove,
@ -318,6 +309,13 @@ var ScrollView = React.createClass({
} else { } else {
ScrollViewClass = AndroidScrollView; ScrollViewClass = AndroidScrollView;
} }
var keyboardDismissModeConstants = {
'none': RCTScrollViewConsts.KeyboardDismissMode.None, // default
'interactive': RCTScrollViewConsts.KeyboardDismissMode.Interactive,
'on-drag': RCTScrollViewConsts.KeyboardDismissMode.OnDrag,
};
props.keyboardDismissMode = props.keyboardDismissMode ?
keyboardDismissModeConstants[props.keyboardDismissMode] : undefined;
} }
invariant( invariant(
ScrollViewClass !== undefined, ScrollViewClass !== undefined,

View File

@ -17,7 +17,7 @@ var StyleSheet = require('StyleSheet');
var Text = require('Text'); var Text = require('Text');
var View = require('View'); var View = require('View');
var Dummy = React.createClass({ var DummySegmentedControlIOS = React.createClass({
render: function() { render: function() {
return ( return (
<View style={[styles.dummy, this.props.style]}> <View style={[styles.dummy, this.props.style]}>
@ -46,4 +46,4 @@ var styles = StyleSheet.create({
} }
}); });
module.exports = Dummy; module.exports = DummySegmentedControlIOS;

View File

@ -108,13 +108,7 @@ var styles = StyleSheet.create({
var RCTSegmentedControl = requireNativeComponent( var RCTSegmentedControl = requireNativeComponent(
'RCTSegmentedControl', 'RCTSegmentedControl',
null SegmentedControlIOS
); );
if (__DEV__) {
verifyPropTypes(
RCTSegmentedControl,
RCTSegmentedControl.viewConfig
);
}
module.exports = SegmentedControlIOS; module.exports = SegmentedControlIOS;

View File

@ -13,26 +13,26 @@
var RCTStatusBarManager = require('NativeModules').StatusBarManager; var RCTStatusBarManager = require('NativeModules').StatusBarManager;
type StatusBarStyle = $Enum<{
'default': string,
'light-content': string,
}>;
type StatusBarAnimation = $Enum<{
'none': string,
'fade': string,
'slide': string,
}>;
var StatusBarIOS = { var StatusBarIOS = {
Style: { setStyle(style: StatusBarStyle, animated?: boolean) {
default: RCTStatusBarManager.Style.default,
lightContent: RCTStatusBarManager.Style.lightContent
},
Animation: {
none: RCTStatusBarManager.Animation.none,
fade: RCTStatusBarManager.Animation.fade,
slide: RCTStatusBarManager.Animation.slide,
},
setStyle(style: number, animated?: boolean) {
animated = animated || false; animated = animated || false;
RCTStatusBarManager.setStyle(style, animated); RCTStatusBarManager.setStyle(style, animated);
}, },
setHidden(hidden: boolean, animation: number) { setHidden(hidden: boolean, animation?: StatusBarAnimation) {
animation = animation || StatusBarIOS.Animation.none; animation = animation || 'none';
RCTStatusBarManager.setHidden(hidden, animation); RCTStatusBarManager.setHidden(hidden, animation);
}, },
}; };

View File

@ -31,10 +31,6 @@ var emptyFunction = require('emptyFunction');
var invariant = require('invariant'); var invariant = require('invariant');
var merge = require('merge'); var merge = require('merge');
var autoCapitalizeConsts = RCTUIManager.UIText.AutocapitalizationType;
var keyboardTypeConsts = RCTUIManager.UIKeyboardType;
var returnKeyTypeConsts = RCTUIManager.UIReturnKeyType;
var RCTTextViewAttributes = merge(ReactNativeViewAttributes.UIView, { var RCTTextViewAttributes = merge(ReactNativeViewAttributes.UIView, {
autoCorrect: true, autoCorrect: true,
autoCapitalize: true, autoCapitalize: true,
@ -96,10 +92,6 @@ var viewConfigAndroid = {
validAttributes: AndroidTextInputAttributes, validAttributes: AndroidTextInputAttributes,
}; };
var crossPlatformKeyboardTypeMap = {
'numeric': 'decimal-pad',
};
type DefaultProps = { type DefaultProps = {
bufferDelay: number; bufferDelay: number;
}; };
@ -171,8 +163,11 @@ var TextInput = React.createClass({
* Determines which keyboard to open, e.g.`numeric`. * Determines which keyboard to open, e.g.`numeric`.
*/ */
keyboardType: PropTypes.oneOf([ keyboardType: PropTypes.oneOf([
// Cross-platform
'default', 'default',
// iOS 'numeric',
'email-address',
// iOS-only
'ascii-capable', 'ascii-capable',
'numbers-and-punctuation', 'numbers-and-punctuation',
'url', 'url',
@ -182,9 +177,6 @@ var TextInput = React.createClass({
'decimal-pad', 'decimal-pad',
'twitter', 'twitter',
'web-search', 'web-search',
// Cross-platform
'numeric',
'email-address',
]), ]),
/** /**
* Determines how the return key should look. * Determines how the return key should look.
@ -426,18 +418,12 @@ var TextInput = React.createClass({
_renderIOS: function() { _renderIOS: function() {
var textContainer; var textContainer;
var autoCapitalize = autoCapitalizeConsts[this.props.autoCapitalize]; var props = this.props;
var clearButtonMode = RCTUIManager.UITextField.clearButtonMode[this.props.clearButtonMode]; props.style = [styles.input, this.props.style];
var keyboardType = keyboardTypeConsts[ if (!props.multiline) {
crossPlatformKeyboardTypeMap[this.props.keyboardType] ||
this.props.keyboardType
];
var returnKeyType = returnKeyTypeConsts[this.props.returnKeyType];
if (!this.props.multiline) {
for (var propKey in onlyMultiline) { for (var propKey in onlyMultiline) {
if (this.props[propKey]) { if (props[propKey]) {
throw new Error( throw new Error(
'TextInput prop `' + propKey + '` is only supported with multiline.' 'TextInput prop `' + propKey + '` is only supported with multiline.'
); );
@ -446,77 +432,48 @@ var TextInput = React.createClass({
textContainer = textContainer =
<RCTTextField <RCTTextField
ref="input" ref="input"
style={[styles.input, this.props.style]} {...props}
enabled={this.props.editable}
keyboardType={keyboardType}
returnKeyType={returnKeyType}
enablesReturnKeyAutomatically={this.props.enablesReturnKeyAutomatically}
secureTextEntry={this.props.password || this.props.secureTextEntry}
onFocus={this._onFocus} onFocus={this._onFocus}
onBlur={this._onBlur} onBlur={this._onBlur}
onChange={this._onChange} onChange={this._onChange}
onEndEditing={this.props.onEndEditing}
onSubmitEditing={this.props.onSubmitEditing}
onSelectionChangeShouldSetResponder={() => true} onSelectionChangeShouldSetResponder={() => true}
onLayout={this.props.onLayout}
placeholder={this.props.placeholder}
placeholderTextColor={this.props.placeholderTextColor}
text={this.state.bufferedValue} text={this.state.bufferedValue}
autoCapitalize={autoCapitalize}
autoCorrect={this.props.autoCorrect}
clearButtonMode={clearButtonMode}
clearTextOnFocus={this.props.clearTextOnFocus}
selectTextOnFocus={this.props.selectTextOnFocus}
/>; />;
} else { } else {
for (var propKey in notMultiline) { for (var propKey in notMultiline) {
if (this.props[propKey]) { if (props[propKey]) {
throw new Error( throw new Error(
'TextInput prop `' + propKey + '` cannot be used with multiline.' 'TextInput prop `' + propKey + '` cannot be used with multiline.'
); );
} }
} }
var children = this.props.children; var children = props.children;
var childCount = 0; var childCount = 0;
ReactChildren.forEach(children, () => ++childCount); ReactChildren.forEach(children, () => ++childCount);
invariant( invariant(
!(this.props.value && childCount), !(props.value && childCount),
'Cannot specify both value and children.' 'Cannot specify both value and children.'
); );
if (childCount > 1) { if (childCount > 1) {
children = <Text>{children}</Text>; children = <Text>{children}</Text>;
} }
if (this.props.inputView) { if (props.inputView) {
children = [children, this.props.inputView]; children = [children, props.inputView];
} }
textContainer = textContainer =
<RCTTextView <RCTTextView
ref="input" ref="input"
style={[styles.input, this.props.style]} {...props}
children={children} children={children}
mostRecentEventCounter={this.state.mostRecentEventCounter} mostRecentEventCounter={this.state.mostRecentEventCounter}
editable={this.props.editable}
keyboardType={keyboardType}
returnKeyType={returnKeyType}
enablesReturnKeyAutomatically={this.props.enablesReturnKeyAutomatically}
secureTextEntry={this.props.password || this.props.secureTextEntry}
onFocus={this._onFocus} onFocus={this._onFocus}
onBlur={this._onBlur} onBlur={this._onBlur}
onChange={this._onChange} onChange={this._onChange}
onEndEditing={this.props.onEndEditing}
onSelectionChange={this._onSelectionChange} onSelectionChange={this._onSelectionChange}
onTextInput={this._onTextInput} onTextInput={this._onTextInput}
onSelectionChangeShouldSetResponder={emptyFunction.thatReturnsTrue} onSelectionChangeShouldSetResponder={emptyFunction.thatReturnsTrue}
onLayout={this.props.onLayout}
placeholder={this.props.placeholder}
placeholderTextColor={this.props.placeholderTextColor}
text={this.state.bufferedValue} text={this.state.bufferedValue}
autoCapitalize={autoCapitalize}
autoCorrect={this.props.autoCorrect}
clearButtonMode={clearButtonMode}
selectTextOnFocus={this.props.selectTextOnFocus}
clearTextOnFocus={this.props.clearTextOnFocus}
/>; />;
} }
@ -524,14 +481,14 @@ var TextInput = React.createClass({
<TouchableWithoutFeedback <TouchableWithoutFeedback
onPress={this._onPress} onPress={this._onPress}
rejectResponderTermination={true} rejectResponderTermination={true}
testID={this.props.testID}> testID={props.testID}>
{textContainer} {textContainer}
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
); );
}, },
_renderAndroid: function() { _renderAndroid: function() {
var autoCapitalize = autoCapitalizeConsts[this.props.autoCapitalize]; var autoCapitalize = RCTUIManager.UIText.AutocapitalizationType[this.props.autoCapitalize];
var children = this.props.children; var children = this.props.children;
var childCount = 0; var childCount = 0;
ReactChildren.forEach(children, () => ++childCount); ReactChildren.forEach(children, () => ++childCount);

View File

@ -23,6 +23,7 @@ var View = require('View');
var cloneWithProps = require('cloneWithProps'); var cloneWithProps = require('cloneWithProps');
var ensureComponentIsNative = require('ensureComponentIsNative'); var ensureComponentIsNative = require('ensureComponentIsNative');
var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
var keyOf = require('keyOf'); var keyOf = require('keyOf');
var merge = require('merge'); var merge = require('merge');
var onlyChild = require('onlyChild'); var onlyChild = require('onlyChild');
@ -111,6 +112,7 @@ var TouchableHighlight = React.createClass({
}, },
componentDidMount: function() { componentDidMount: function() {
ensurePositiveDelayProps(this.props);
ensureComponentIsNative(this.refs[CHILD_REF]); ensureComponentIsNative(this.refs[CHILD_REF]);
}, },
@ -119,6 +121,7 @@ var TouchableHighlight = React.createClass({
}, },
componentWillReceiveProps: function(nextProps) { componentWillReceiveProps: function(nextProps) {
ensurePositiveDelayProps(nextProps);
if (nextProps.activeOpacity !== this.props.activeOpacity || if (nextProps.activeOpacity !== this.props.activeOpacity ||
nextProps.underlayColor !== this.props.underlayColor || nextProps.underlayColor !== this.props.underlayColor ||
nextProps.style !== this.props.style) { nextProps.style !== this.props.style) {
@ -152,7 +155,8 @@ var TouchableHighlight = React.createClass({
touchableHandlePress: function() { touchableHandlePress: function() {
this.clearTimeout(this._hideTimeout); this.clearTimeout(this._hideTimeout);
this._showUnderlay(); this._showUnderlay();
this._hideTimeout = this.setTimeout(this._hideUnderlay, 100); this._hideTimeout = this.setTimeout(this._hideUnderlay,
this.props.delayPressOut || 100);
this.props.onPress && this.props.onPress(); this.props.onPress && this.props.onPress();
}, },
@ -164,6 +168,18 @@ var TouchableHighlight = React.createClass({
return PRESS_RECT_OFFSET; // Always make sure to predeclare a constant! return PRESS_RECT_OFFSET; // Always make sure to predeclare a constant!
}, },
touchableGetHighlightDelayMS: function() {
return this.props.delayPressIn;
},
touchableGetLongPressDelayMS: function() {
return this.props.delayLongPress;
},
touchableGetPressOutDelayMS: function() {
return this.props.delayPressOut;
},
_showUnderlay: function() { _showUnderlay: function() {
this.refs[UNDERLAY_REF].setNativeProps(this.state.activeUnderlayProps); this.refs[UNDERLAY_REF].setNativeProps(this.state.activeUnderlayProps);
this.refs[CHILD_REF].setNativeProps(this.state.activeProps); this.refs[CHILD_REF].setNativeProps(this.state.activeProps);

View File

@ -15,11 +15,13 @@
var NativeMethodsMixin = require('NativeMethodsMixin'); var NativeMethodsMixin = require('NativeMethodsMixin');
var POPAnimationMixin = require('POPAnimationMixin'); var POPAnimationMixin = require('POPAnimationMixin');
var React = require('React'); var React = require('React');
var TimerMixin = require('react-timer-mixin');
var Touchable = require('Touchable'); var Touchable = require('Touchable');
var TouchableWithoutFeedback = require('TouchableWithoutFeedback'); var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
var cloneWithProps = require('cloneWithProps'); var cloneWithProps = require('cloneWithProps');
var ensureComponentIsNative = require('ensureComponentIsNative'); var ensureComponentIsNative = require('ensureComponentIsNative');
var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
var flattenStyle = require('flattenStyle'); var flattenStyle = require('flattenStyle');
var keyOf = require('keyOf'); var keyOf = require('keyOf');
var onlyChild = require('onlyChild'); var onlyChild = require('onlyChild');
@ -50,7 +52,7 @@ var onlyChild = require('onlyChild');
*/ */
var TouchableOpacity = React.createClass({ var TouchableOpacity = React.createClass({
mixins: [Touchable.Mixin, NativeMethodsMixin, POPAnimationMixin], mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin, POPAnimationMixin],
propTypes: { propTypes: {
...TouchableWithoutFeedback.propTypes, ...TouchableWithoutFeedback.propTypes,
@ -72,6 +74,7 @@ var TouchableOpacity = React.createClass({
}, },
componentDidMount: function() { componentDidMount: function() {
ensurePositiveDelayProps(this.props);
ensureComponentIsNative(this.refs[CHILD_REF]); ensureComponentIsNative(this.refs[CHILD_REF]);
}, },
@ -79,6 +82,10 @@ var TouchableOpacity = React.createClass({
ensureComponentIsNative(this.refs[CHILD_REF]); ensureComponentIsNative(this.refs[CHILD_REF]);
}, },
componentWillReceiveProps: function(nextProps) {
ensurePositiveDelayProps(nextProps);
},
setOpacityTo: function(value) { setOpacityTo: function(value) {
if (POPAnimationMixin) { if (POPAnimationMixin) {
// Reset with animation if POP is available // Reset with animation if POP is available
@ -86,6 +93,7 @@ var TouchableOpacity = React.createClass({
var anim = { var anim = {
type: this.AnimationTypes.linear, type: this.AnimationTypes.linear,
property: this.AnimationProperties.opacity, property: this.AnimationProperties.opacity,
duration: 0.15,
toValue: value, toValue: value,
}; };
this.startAnimation(CHILD_REF, anim); this.startAnimation(CHILD_REF, anim);
@ -102,20 +110,26 @@ var TouchableOpacity = React.createClass({
* defined on your component. * defined on your component.
*/ */
touchableHandleActivePressIn: function() { touchableHandleActivePressIn: function() {
this.refs[CHILD_REF].setNativeProps({ this.clearTimeout(this._hideTimeout);
opacity: this.props.activeOpacity this._hideTimeout = null;
}); this._opacityActive();
this.props.onPressIn && this.props.onPressIn(); this.props.onPressIn && this.props.onPressIn();
}, },
touchableHandleActivePressOut: function() { touchableHandleActivePressOut: function() {
var child = onlyChild(this.props.children); if (!this._hideTimeout) {
var childStyle = flattenStyle(child.props.style) || {}; this._opacityInactive();
this.setOpacityTo(childStyle.opacity === undefined ? 1 : childStyle.opacity); }
this.props.onPressOut && this.props.onPressOut(); this.props.onPressOut && this.props.onPressOut();
}, },
touchableHandlePress: function() { touchableHandlePress: function() {
this.clearTimeout(this._hideTimeout);
this._opacityActive();
this._hideTimeout = this.setTimeout(
this._opacityInactive,
this.props.delayPressOut || 100
);
this.props.onPress && this.props.onPress(); this.props.onPress && this.props.onPress();
}, },
@ -128,7 +142,30 @@ var TouchableOpacity = React.createClass({
}, },
touchableGetHighlightDelayMS: function() { touchableGetHighlightDelayMS: function() {
return 0; return this.props.delayPressIn || 0;
},
touchableGetLongPressDelayMS: function() {
return this.props.delayLongPress === 0 ? 0 :
this.props.delayLongPress || 500;
},
touchableGetPressOutDelayMS: function() {
return this.props.delayPressOut;
},
_opacityActive: function() {
this.setOpacityTo(this.props.activeOpacity);
},
_opacityInactive: function() {
this.clearTimeout(this._hideTimeout);
this._hideTimeout = null;
var child = onlyChild(this.props.children);
var childStyle = flattenStyle(child.props.style) || {};
this.setOpacityTo(
childStyle.opacity === undefined ? 1 : childStyle.opacity
);
}, },
render: function() { render: function() {

View File

@ -12,7 +12,9 @@
'use strict'; 'use strict';
var React = require('React'); var React = require('React');
var TimerMixin = require('react-timer-mixin');
var Touchable = require('Touchable'); var Touchable = require('Touchable');
var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
var onlyChild = require('onlyChild'); var onlyChild = require('onlyChild');
/** /**
@ -31,23 +33,44 @@ type Event = Object;
* one of the primary reason a "web" app doesn't feel "native". * one of the primary reason a "web" app doesn't feel "native".
*/ */
var TouchableWithoutFeedback = React.createClass({ var TouchableWithoutFeedback = React.createClass({
mixins: [Touchable.Mixin], mixins: [TimerMixin, Touchable.Mixin],
propTypes: { propTypes: {
/** /**
* Called when the touch is released, but not if cancelled (e.g. by a scroll * Called when the touch is released, but not if cancelled (e.g. by a scroll
* that steals the responder lock). * that steals the responder lock).
*/ */
accessible: React.PropTypes.bool,
onPress: React.PropTypes.func, onPress: React.PropTypes.func,
onPressIn: React.PropTypes.func, onPressIn: React.PropTypes.func,
onPressOut: React.PropTypes.func, onPressOut: React.PropTypes.func,
onLongPress: React.PropTypes.func, onLongPress: React.PropTypes.func,
/**
* Delay in ms, from the start of the touch, before onPressIn is called.
*/
delayPressIn: React.PropTypes.number,
/**
* Delay in ms, from the release of the touch, before onPressOut is called.
*/
delayPressOut: React.PropTypes.number,
/**
* Delay in ms, from onPressIn, before onLongPress is called.
*/
delayLongPress: React.PropTypes.number,
}, },
getInitialState: function() { getInitialState: function() {
return this.touchableGetInitialState(); return this.touchableGetInitialState();
}, },
componentDidMount: function() {
ensurePositiveDelayProps(this.props);
},
componentWillReceiveProps: function(nextProps: Object) {
ensurePositiveDelayProps(nextProps);
},
/** /**
* `Touchable.Mixin` self callbacks. The mixin will invoke these if they are * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
* defined on your component. * defined on your component.
@ -73,13 +96,22 @@ var TouchableWithoutFeedback = React.createClass({
}, },
touchableGetHighlightDelayMS: function(): number { touchableGetHighlightDelayMS: function(): number {
return 0; return this.props.delayPressIn || 0;
},
touchableGetLongPressDelayMS: function(): number {
return this.props.delayLongPress === 0 ? 0 :
this.props.delayLongPress || 500;
},
touchableGetPressOutDelayMS: function(): number {
return this.props.delayPressOut || 0;
}, },
render: function(): ReactElement { render: function(): ReactElement {
// Note(avik): remove dynamic typecast once Flow has been upgraded // Note(avik): remove dynamic typecast once Flow has been upgraded
return (React: any).cloneElement(onlyChild(this.props.children), { return (React: any).cloneElement(onlyChild(this.props.children), {
accessible: true, accessible: this.props.accessible !== false,
testID: this.props.testID, testID: this.props.testID,
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder, onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest, onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,

View File

@ -0,0 +1,24 @@
/**
* 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.
*
* @providesModule ensurePositiveDelayProps
* @flow
*/
'use strict';
var invariant = require('invariant');
var ensurePositiveDelayProps = function(props: any) {
invariant(
!(props.delayPressIn < 0 || props.delayPressOut < 0 ||
props.delayLongPress < 0),
'Touchable components cannot have negative delay properties'
);
};
module.exports = ensurePositiveDelayProps;

View File

@ -146,20 +146,11 @@ var Image = React.createClass({
if (this.props.style && this.props.style.tintColor) { if (this.props.style && this.props.style.tintColor) {
warning(RawImage === RCTStaticImage, 'tintColor style only supported on static images.'); warning(RawImage === RCTStaticImage, 'tintColor style only supported on static images.');
} }
var resizeMode = this.props.resizeMode || style.resizeMode; var resizeMode = this.props.resizeMode || style.resizeMode || 'cover';
var contentModes = NativeModules.UIManager.UIView.ContentMode;
var contentMode;
if (resizeMode === ImageResizeMode.stretch) {
contentMode = contentModes.ScaleToFill;
} else if (resizeMode === ImageResizeMode.contain) {
contentMode = contentModes.ScaleAspectFit;
} else { // ImageResizeMode.cover or undefined
contentMode = contentModes.ScaleAspectFill;
}
var nativeProps = merge(this.props, { var nativeProps = merge(this.props, {
style, style,
contentMode, resizeMode,
tintColor: style.tintColor, tintColor: style.tintColor,
}); });
if (isStored) { if (isStored) {
@ -187,7 +178,7 @@ var nativeOnlyProps = {
src: true, src: true,
defaultImageSrc: true, defaultImageSrc: true,
imageTag: true, imageTag: true,
contentMode: true, resizeMode: true,
}; };
if (__DEV__) { if (__DEV__) {
verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps); verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps);

View File

@ -82,8 +82,8 @@ static NSString *RCTCacheKeyForURL(NSURL *url)
RCTImageDownloader *strongSelf = weakSelf; RCTImageDownloader *strongSelf = weakSelf;
NSArray *blocks = strongSelf->_pendingBlocks[cacheKey]; NSArray *blocks = strongSelf->_pendingBlocks[cacheKey];
[strongSelf->_pendingBlocks removeObjectForKey:cacheKey]; [strongSelf->_pendingBlocks removeObjectForKey:cacheKey];
for (RCTCachedDataDownloadBlock block in blocks) { for (RCTCachedDataDownloadBlock cacheDownloadBlock in blocks) {
block(cached, data, error); cacheDownloadBlock(cached, data, error);
} }
}); });
}; };

View File

@ -29,6 +29,6 @@ RCT_EXPORT_MODULE()
RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage) RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage)
RCT_REMAP_VIEW_PROPERTY(src, imageURL, NSURL) RCT_REMAP_VIEW_PROPERTY(src, imageURL, NSURL)
RCT_EXPORT_VIEW_PROPERTY(contentMode, UIViewContentMode) RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
@end @end

View File

@ -26,7 +26,7 @@ RCT_EXPORT_MODULE()
} }
RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets) RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
RCT_EXPORT_VIEW_PROPERTY(contentMode, UIViewContentMode) RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
RCT_CUSTOM_VIEW_PROPERTY(src, NSURL, RCTStaticImage) RCT_CUSTOM_VIEW_PROPERTY(src, NSURL, RCTStaticImage)
{ {
if (json) { if (json) {

View File

@ -50,6 +50,9 @@ function findInstanceByNativeTag(rootTag, nativeTag) {
var containerID = ReactNativeTagHandles.tagToRootNodeID[rootTag]; var containerID = ReactNativeTagHandles.tagToRootNodeID[rootTag];
var rootInstance = ReactNativeMount._instancesByContainerID[containerID]; var rootInstance = ReactNativeMount._instancesByContainerID[containerID];
var targetID = ReactNativeTagHandles.tagToRootNodeID[nativeTag]; var targetID = ReactNativeTagHandles.tagToRootNodeID[nativeTag];
if (!targetID) {
return undefined;
}
return findInstance(rootInstance, targetID); return findInstance(rootInstance, targetID);
} }

View File

@ -20,8 +20,7 @@ var RCTPickerIOSConsts = require('NativeModules').UIManager.RCTPicker.Constants;
var StyleSheet = require('StyleSheet'); var StyleSheet = require('StyleSheet');
var View = require('View'); var View = require('View');
var createReactNativeComponentClass = var requireNativeComponent = require('requireNativeComponent');
require('createReactNativeComponentClass');
var merge = require('merge'); var merge = require('merge');
var PICKER = 'picker'; var PICKER = 'picker';
@ -60,7 +59,7 @@ var PickerIOS = React.createClass({
<View style={this.props.style}> <View style={this.props.style}>
<RCTPickerIOS <RCTPickerIOS
ref={PICKER} ref={PICKER}
style={styles.rkPickerIOS} style={styles.pickerIOS}
items={this.state.items} items={this.state.items}
selectedIndex={this.state.selectedIndex} selectedIndex={this.state.selectedIndex}
onChange={this._onChange} onChange={this._onChange}
@ -104,7 +103,7 @@ PickerIOS.Item = React.createClass({
}); });
var styles = StyleSheet.create({ var styles = StyleSheet.create({
rkPickerIOS: { pickerIOS: {
// The picker will conform to whatever width is given, but we do // The picker will conform to whatever width is given, but we do
// have to set the component's height explicitly on the // have to set the component's height explicitly on the
// surrounding view to ensure it gets rendered. // surrounding view to ensure it gets rendered.
@ -112,14 +111,6 @@ var styles = StyleSheet.create({
}, },
}); });
var rkPickerIOSAttributes = merge(ReactNativeViewAttributes.UIView, { var RCTPickerIOS = requireNativeComponent('RCTPicker', null);
items: true,
selectedIndex: true,
});
var RCTPickerIOS = createReactNativeComponentClass({
validAttributes: rkPickerIOSAttributes,
uiViewClassName: 'RCTPicker',
});
module.exports = PickerIOS; module.exports = PickerIOS;

View File

@ -20,6 +20,7 @@ var _initialNotification = RCTPushNotificationManager &&
RCTPushNotificationManager.initialNotification; RCTPushNotificationManager.initialNotification;
var DEVICE_NOTIF_EVENT = 'remoteNotificationReceived'; var DEVICE_NOTIF_EVENT = 'remoteNotificationReceived';
var NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered';
/** /**
* Handle push notifications for your app, including permission handling and * Handle push notifications for your app, including permission handling and
@ -49,30 +50,72 @@ class PushNotificationIOS {
} }
/** /**
* Attaches a listener to remote notifications while the app is running in the * Attaches a listener to remote notification events while the app is running
* foreground or the background. * in the foreground or the background.
* *
* The handler will get be invoked with an instance of `PushNotificationIOS` * Valid events are:
*
* - `notification` : Fired when a remote notification is received. The
* handler will be invoked with an instance of `PushNotificationIOS`.
* - `register`: Fired when the user registers for remote notifications. The
* handler will be invoked with a hex string representing the deviceToken.
*/ */
static addEventListener(type: string, handler: Function) { static addEventListener(type: string, handler: Function) {
invariant( invariant(
type === 'notification', type === 'notification' || type === 'register',
'PushNotificationIOS only supports `notification` events' 'PushNotificationIOS only supports `notification` and `register` events'
); );
if (type === 'notification') {
_notifHandlers[handler] = RCTDeviceEventEmitter.addListener( _notifHandlers[handler] = RCTDeviceEventEmitter.addListener(
DEVICE_NOTIF_EVENT, DEVICE_NOTIF_EVENT,
(notifData) => { (notifData) => {
handler(new PushNotificationIOS(notifData)); handler(new PushNotificationIOS(notifData));
} }
); );
} else if (type === 'register') {
_notifHandlers[handler] = RCTDeviceEventEmitter.addListener(
NOTIF_REGISTER_EVENT,
(registrationInfo) => {
handler(registrationInfo.deviceToken);
}
);
}
} }
/** /**
* Requests all notification permissions from iOS, prompting the user's * Requests notification permissions from iOS, prompting the user's
* dialog box. * dialog box. By default, it will request all notification permissions, but
* a subset of these can be requested by passing a map of requested
* permissions.
* The following permissions are supported:
*
* - `alert`
* - `badge`
* - `sound`
*
* If a map is provided to the method, only the permissions with truthy values
* will be requested.
*/ */
static requestPermissions() { static requestPermissions(permissions?: {
RCTPushNotificationManager.requestPermissions(); alert?: boolean,
badge?: boolean,
sound?: boolean
}) {
var requestedPermissions = {};
if (permissions) {
requestedPermissions = {
alert: !!permissions.alert,
badge: !!permissions.badge,
sound: !!permissions.sound
};
} else {
requestedPermissions = {
alert: true,
badge: true,
sound: true
};
}
RCTPushNotificationManager.requestPermissions(requestedPermissions);
} }
/** /**
@ -97,8 +140,8 @@ class PushNotificationIOS {
*/ */
static removeEventListener(type: string, handler: Function) { static removeEventListener(type: string, handler: Function) {
invariant( invariant(
type === 'notification', type === 'notification' || type === 'register',
'PushNotificationIOS only supports `notification` events' 'PushNotificationIOS only supports `notification` and `register` events'
); );
if (!_notifHandlers[handler]) { if (!_notifHandlers[handler]) {
return; return;

View File

@ -14,6 +14,7 @@
@interface RCTPushNotificationManager : NSObject <RCTBridgeModule> @interface RCTPushNotificationManager : NSObject <RCTBridgeModule>
+ (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings; + (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings;
+ (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
+ (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification; + (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification;
@end @end

View File

@ -12,7 +12,18 @@
#import "RCTBridge.h" #import "RCTBridge.h"
#import "RCTEventDispatcher.h" #import "RCTEventDispatcher.h"
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
#define UIUserNotificationTypeAlert UIRemoteNotificationTypeAlert
#define UIUserNotificationTypeBadge UIRemoteNotificationTypeBadge
#define UIUserNotificationTypeSound UIRemoteNotificationTypeSound
#define UIUserNotificationTypeNone UIRemoteNotificationTypeNone
#define UIUserNotificationType UIRemoteNotificationType
#endif
NSString *const RCTRemoteNotificationReceived = @"RemoteNotificationReceived"; NSString *const RCTRemoteNotificationReceived = @"RemoteNotificationReceived";
NSString *const RCTRemoteNotificationsRegistered = @"RemoteNotificationsRegistered";
@implementation RCTPushNotificationManager @implementation RCTPushNotificationManager
{ {
@ -30,6 +41,10 @@ RCT_EXPORT_MODULE()
selector:@selector(handleRemoteNotificationReceived:) selector:@selector(handleRemoteNotificationReceived:)
name:RCTRemoteNotificationReceived name:RCTRemoteNotificationReceived
object:nil]; object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleRemoteNotificationsRegistered:)
name:RCTRemoteNotificationsRegistered
object:nil];
} }
return self; return self;
} }
@ -52,6 +67,21 @@ RCT_EXPORT_MODULE()
} }
} }
+ (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSMutableString *hexString = [NSMutableString string];
const unsigned char *bytes = [deviceToken bytes];
for (int i = 0; i < [deviceToken length]; i++) {
[hexString appendFormat:@"%02x", bytes[i]];
}
NSDictionary *userInfo = @{
@"deviceToken" : [hexString copy]
};
[[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationsRegistered
object:self
userInfo:userInfo];
}
+ (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification + (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification
{ {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationReceived [[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationReceived
@ -65,6 +95,12 @@ RCT_EXPORT_MODULE()
body:[notification userInfo]]; body:[notification userInfo]];
} }
- (void)handleRemoteNotificationsRegistered:(NSNotification *)notification
{
[_bridge.eventDispatcher sendDeviceEventWithName:@"remoteNotificationsRegistered"
body:[notification userInfo]];
}
/** /**
* Update the application icon badge number on the home screen * Update the application icon badge number on the home screen
*/ */
@ -83,36 +119,35 @@ RCT_EXPORT_METHOD(getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback
]); ]);
} }
RCT_EXPORT_METHOD(requestPermissions) RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions)
{ {
Class _UIUserNotificationSettings; UIUserNotificationType types = UIRemoteNotificationTypeNone;
if ((_UIUserNotificationSettings = NSClassFromString(@"UIUserNotificationSettings"))) { if (permissions) {
UIUserNotificationType types = UIUserNotificationTypeSound | UIUserNotificationTypeBadge | UIUserNotificationTypeAlert; if ([permissions[@"alert"] boolValue]) {
UIUserNotificationSettings *notificationSettings = [_UIUserNotificationSettings settingsForTypes:types categories:nil]; types |= UIUserNotificationTypeAlert;
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings]; }
if ([permissions[@"badge"] boolValue]) {
types |= UIUserNotificationTypeBadge;
}
if ([permissions[@"sound"] boolValue]) {
types |= UIUserNotificationTypeSound;
}
} else { } else {
types = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
}
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0
id notificationSettings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
[[UIApplication sharedApplication] registerForRemoteNotificationTypes: [[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert]; [[UIApplication sharedApplication] registerForRemoteNotifications];
#else
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:types];
#endif #endif
} }
}
RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback) RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback)
{ {
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
#define UIUserNotificationTypeAlert UIRemoteNotificationTypeAlert
#define UIUserNotificationTypeBadge UIRemoteNotificationTypeBadge
#define UIUserNotificationTypeSound UIRemoteNotificationTypeSound
#endif
NSUInteger types = 0; NSUInteger types = 0;
if ([UIApplication instancesRespondToSelector:@selector(currentUserNotificationSettings)]) { if ([UIApplication instancesRespondToSelector:@selector(currentUserNotificationSettings)]) {
types = [[[UIApplication sharedApplication] currentUserNotificationSettings] types]; types = [[[UIApplication sharedApplication] currentUserNotificationSettings] types];

View File

@ -35,7 +35,11 @@
sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"]; sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"];
_testController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName]; _testController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName];
_testController.referenceImagesDirectory = referenceDir; _testController.referenceImagesDirectory = referenceDir;
#if RUNNING_ON_CI
_scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"];
#else
_scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]]; _scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]];
#endif
} }
return self; return self;
} }

View File

@ -0,0 +1,38 @@
/**
* 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.
*
* @providesModule BorderBox
* @flow
*/
'use strict';
var React = require('React');
var View = require('View');
class BorderBox extends React.Component {
render() {
var box = this.props.box;
if (!box) {
return this.props.children;
}
var style = {
borderTopWidth: box.top,
borderBottomWidth: box.bottom,
borderLeftWidth: box.left,
borderRightWidth: box.right,
};
return (
<View style={[style, this.props.style]}>
{this.props.children}
</View>
);
}
}
module.exports = BorderBox;

View File

@ -0,0 +1,113 @@
/**
* 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.
*
* @providesModule BoxInspector
* @flow
*/
'use strict';
var React = require('React');
var StyleSheet = require('StyleSheet');
var Text = require('Text');
var View = require('View');
var resolveBoxStyle = require('resolveBoxStyle');
var blank = {
top: 0,
left: 0,
right: 0,
bottom: 0,
};
class BoxInspector extends React.Component {
render() {
var frame = this.props.frame;
var style = this.props.style;
var margin = style && resolveBoxStyle('margin', style) || blank;
var padding = style && resolveBoxStyle('padding', style) || blank;
return (
<BoxContainer title="margin" titleStyle={styles.marginLabel} box={margin}>
<BoxContainer title="padding" box={padding}>
<View>
<Text style={styles.innerText}>
({frame.left}, {frame.top})
</Text>
<Text style={styles.innerText}>
{frame.width} &times; {frame.height}
</Text>
</View>
</BoxContainer>
</BoxContainer>
);
}
}
class BoxContainer extends React.Component {
render() {
var box = this.props.box;
return (
<View style={styles.box}>
<View style={styles.row}>
<Text style={[this.props.titleStyle, styles.label]}>{this.props.title}</Text>
<Text style={styles.boxText}>{box.top}</Text>
</View>
<View style={styles.row}>
<Text style={styles.boxText}>{box.left}</Text>
{this.props.children}
<Text style={styles.boxText}>{box.right}</Text>
</View>
<Text style={styles.boxText}>{box.bottom}</Text>
</View>
);
}
}
var styles = StyleSheet.create({
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
},
marginLabel: {
width: 60,
},
label: {
fontSize: 10,
color: 'rgb(255,100,0)',
marginLeft: 5,
flex: 1,
textAlign: 'left',
top: -3,
},
buffer: {
fontSize: 10,
color: 'yellow',
flex: 1,
textAlign: 'center',
},
innerText: {
color: 'yellow',
fontSize: 12,
textAlign: 'center',
width: 70,
},
box: {
borderWidth: 1,
borderColor: 'grey',
},
boxText: {
color: 'white',
fontSize: 12,
marginHorizontal: 3,
marginVertical: 2,
textAlign: 'center',
},
});
module.exports = BoxInspector;

View File

@ -0,0 +1,74 @@
/**
* 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.
*
* @providesModule ElementBox
* @flow
*/
'use strict';
var React = require('React');
var View = require('View');
var StyleSheet = require('StyleSheet');
var BorderBox = require('BorderBox');
var resolveBoxStyle = require('resolveBoxStyle');
var flattenStyle = require('flattenStyle');
class ElementBox extends React.Component {
render() {
var style = flattenStyle(this.props.style) || {};
var margin = resolveBoxStyle('margin', style);
var padding = resolveBoxStyle('padding', style);
var frameStyle = this.props.frame;
if (margin) {
frameStyle = {
top: frameStyle.top - margin.top,
left: frameStyle.left - margin.left,
height: frameStyle.height + margin.top + margin.bottom,
width: frameStyle.width + margin.left + margin.right,
};
}
var contentStyle = {
width: this.props.frame.width,
height: this.props.frame.height,
};
if (padding) {
contentStyle = {
width: contentStyle.width - padding.left - padding.right,
height: contentStyle.height - padding.top - padding.bottom,
};
}
return (
<View style={[styles.frame, frameStyle]} pointerEvents="none">
<BorderBox box={margin} style={styles.margin}>
<BorderBox box={padding} style={styles.padding}>
<View style={[styles.content, contentStyle]} />
</BorderBox>
</BorderBox>
</View>
);
}
}
var styles = StyleSheet.create({
frame: {
position: 'absolute',
},
content: {
backgroundColor: 'rgba(200, 230, 255, 0.8)',
},
padding: {
borderColor: 'rgba(77, 255, 0, 0.3)',
},
margin: {
borderColor: 'rgba(255, 132, 0, 0.3)',
},
});
module.exports = ElementBox;

View File

@ -0,0 +1,105 @@
/**
* 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.
*
* @providesModule ElementProperties
* @flow
*/
'use strict';
var React = require('React');
var StyleSheet = require('StyleSheet');
var Text = require('Text');
var View = require('View');
var PropTypes = require('ReactPropTypes');
var BoxInspector = require('BoxInspector');
var StyleInspector = require('StyleInspector');
var TouchableHighlight = require('TouchableHighlight');
var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
var flattenStyle = require('flattenStyle');
var mapWithSeparator = require('mapWithSeparator');
var ElementProperties = React.createClass({
propTypes: {
hierarchy: PropTypes.array.isRequired,
style: PropTypes.array.isRequired,
},
render: function() {
var style = flattenStyle(this.props.style);
var selection = this.props.selection;
// Without the `TouchableWithoutFeedback`, taps on this inspector pane
// would change the inspected element to whatever is under the inspector
return (
<TouchableWithoutFeedback>
<View style={styles.info}>
<View style={styles.breadcrumb}>
{mapWithSeparator(
this.props.hierarchy,
(item, i) => (
<TouchableHighlight
style={[styles.breadItem, i === selection && styles.selected]}
onPress={() => this.props.setSelection(i)}>
<Text style={styles.breadItemText}>
{item.getName ? item.getName() : 'Unknown'}
</Text>
</TouchableHighlight>
),
() => <Text style={styles.breadSep}>&#9656;</Text>
)}
</View>
<View style={styles.row}>
<StyleInspector style={style} />
<BoxInspector style={style} frame={this.props.frame} />
</View>
</View>
</TouchableWithoutFeedback>
);
}
});
var styles = StyleSheet.create({
breadSep: {
fontSize: 8,
color: 'white',
},
breadcrumb: {
flexDirection: 'row',
flexWrap: 'wrap',
marginBottom: 5,
},
selected: {
borderColor: 'white',
borderRadius: 5,
},
breadItem: {
borderWidth: 1,
borderColor: 'transparent',
marginHorizontal: 2,
},
breadItemText: {
fontSize: 10,
color: 'white',
marginHorizontal: 5,
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
info: {
backgroundColor: 'rgba(0, 0, 0, 0.7)',
padding: 10,
},
path: {
color: 'white',
fontSize: 9,
},
});
module.exports = ElementProperties;

View File

@ -17,12 +17,16 @@ var StyleSheet = require('StyleSheet');
var Text = require('Text'); var Text = require('Text');
var UIManager = require('NativeModules').UIManager; var UIManager = require('NativeModules').UIManager;
var View = require('View'); var View = require('View');
var ElementBox = require('ElementBox');
var ElementProperties = require('ElementProperties');
var InspectorOverlay = React.createClass({ var InspectorOverlay = React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
frame: null, frame: null,
pointerY: 0,
hierarchy: [], hierarchy: [],
selection: -1,
}; };
}, },
@ -33,15 +37,34 @@ var InspectorOverlay = React.createClass({
[locationX, locationY], [locationX, locationY],
(nativeViewTag, left, top, width, height) => { (nativeViewTag, left, top, width, height) => {
var instance = Inspector.findInstanceByNativeTag(this.props.rootTag, nativeViewTag); var instance = Inspector.findInstanceByNativeTag(this.props.rootTag, nativeViewTag);
if (!instance) {
return;
}
var hierarchy = Inspector.getOwnerHierarchy(instance); var hierarchy = Inspector.getOwnerHierarchy(instance);
var publicInstance = instance.getPublicInstance();
this.setState({ this.setState({
hierarchy, hierarchy,
frame: {left, top, width, height} pointerY: locationY,
selection: hierarchy.length - 1,
frame: {left, top, width, height},
style: publicInstance.props ? publicInstance.props.style : {},
}); });
} }
); );
}, },
setSelection(i) {
var instance = this.state.hierarchy[i];
var publicInstance = instance.getPublicInstance();
UIManager.measure(React.findNodeHandle(instance), (x, y, width, height, left, top) => {
this.setState({
frame: {left, top, width, height},
style: publicInstance.props ? publicInstance.props.style : {},
selection: i,
});
});
},
shouldSetResponser: function(e) { shouldSetResponser: function(e) {
this.findViewForTouchEvent(e); this.findViewForTouchEvent(e);
return true; return true;
@ -49,18 +72,32 @@ var InspectorOverlay = React.createClass({
render: function() { render: function() {
var content = []; var content = [];
var justifyContent = 'flex-end';
if (this.state.frame) { if (this.state.frame) {
var distanceToTop = this.state.frame.top; var distanceToTop = this.state.pointerY;
var distanceToBottom = Dimensions.get('window').height - var distanceToBottom = Dimensions.get('window').height - distanceToTop;
(this.state.frame.top + this.state.frame.height);
var justifyContent = distanceToTop > distanceToBottom justifyContent = distanceToTop > distanceToBottom
? 'flex-start' ? 'flex-start'
: 'flex-end'; : 'flex-end';
content.push(<View pointerEvents="none" style={[styles.frame, this.state.frame]} />); content.push(<ElementBox frame={this.state.frame} style={this.state.style} />);
content.push(<ElementProperties hierarchy={this.state.hierarchy} />); content.push(
<ElementProperties
style={this.state.style}
frame={this.state.frame}
hierarchy={this.state.hierarchy}
selection={this.state.selection}
setSelection={this.setSelection}
/>
);
} else {
content.push(
<View style={styles.welcomeMessage}>
<Text style={styles.welcomeText}>Welcome to the inspector! Tap something to inspect it.</Text>
</View>
);
} }
return ( return (
<View <View
@ -73,42 +110,23 @@ var InspectorOverlay = React.createClass({
} }
}); });
var ElementProperties = React.createClass({
render: function() {
var path = this.props.hierarchy.map((instance) => {
return instance.getName ? instance.getName() : 'Unknown';
}).join(' > ');
return (
<View style={styles.info}>
<Text style={styles.path}>
{path}
</Text>
</View>
);
}
});
var styles = StyleSheet.create({ var styles = StyleSheet.create({
welcomeMessage: {
backgroundColor: 'rgba(0, 0, 0, 0.7)',
padding: 10,
paddingVertical: 50,
},
welcomeText: {
color: 'white',
},
inspector: { inspector: {
backgroundColor: 'rgba(255,255,255,0.8)', backgroundColor: 'rgba(255,255,255,0.0)',
position: 'absolute', position: 'absolute',
left: 0, left: 0,
top: 0, top: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
}, },
frame: {
position: 'absolute',
backgroundColor: 'rgba(155,155,255,0.3)',
},
info: {
backgroundColor: 'rgba(0, 0, 0, 0.7)',
padding: 10,
},
path: {
color: 'white',
fontSize: 9,
}
}); });
module.exports = InspectorOverlay; module.exports = InspectorOverlay;

View File

@ -0,0 +1,63 @@
/**
* 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.
*
* @providesModule StyleInspector
* @flow
*/
'use strict';
var React = require('React');
var StyleSheet = require('StyleSheet');
var Text = require('Text');
var View = require('View');
class StyleInspector extends React.Component {
render() {
if (!this.props.style) {
return <Text style={styles.noStyle}>No style</Text>;
}
var names = Object.keys(this.props.style);
return (
<View style={styles.container}>
<View>
{names.map(name => <Text style={styles.attr}>{name}:</Text>)}
</View>
<View>
{names.map(name => <Text style={styles.value}>{this.props.style[name]}</Text>)}
</View>
</View>
);
}
}
var styles = StyleSheet.create({
container: {
flexDirection: 'row',
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
},
attr: {
fontSize: 10,
color: '#ccc',
},
value: {
fontSize: 10,
color: 'white',
marginLeft: 10,
},
noStyle: {
color: 'white',
fontSize: 10,
},
});
module.exports = StyleInspector;

View File

@ -0,0 +1,59 @@
/**
* 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.
*
* @providesModule resolveBoxStyle
* @flow
*/
'use strict';
/**
* Resolve a style property into it's component parts, e.g.
*
* resolveProperties('margin', {margin: 5, marginBottom: 10})
* ->
* {top: 5, left: 5, right: 5, bottom: 10}
*
* If none are set, returns false.
*/
function resolveBoxStyle(prefix: String, style: Object): ?Object {
var res = {};
var subs = ['top', 'left', 'bottom', 'right'];
var set = false;
subs.forEach(sub => {
res[sub] = style[prefix] || 0;
});
if (style[prefix]) {
set = true;
}
if (style[prefix + 'Vertical']) {
res.top = res.bottom = style[prefix + 'Vertical'];
set = true;
}
if (style[prefix + 'Horizontal']) {
res.left = res.right = style[prefix + 'Horizontal'];
set = true;
}
subs.forEach(sub => {
var val = style[prefix + capFirst(sub)];
if (val) {
res[sub] = val;
set = true;
}
});
if (!set) {
return;
}
return res;
}
function capFirst(text) {
return text[0].toUpperCase() + text.slice(1);
}
module.exports = resolveBoxStyle;

View File

@ -0,0 +1,19 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule mapWithSeparator
*/
'use strict';
function mapWithSeparator(array, valueFunction, separatorFunction) {
var results = [];
for (var i = 0; i < array.length; i++) {
results.push(valueFunction(array[i], i, array));
if (i !== array.length - 1) {
results.push(separatorFunction(i));
}
}
return results;
}
module.exports = mapWithSeparator;

View File

@ -24,11 +24,12 @@ var ReactNative = Object.assign(Object.create(require('React')), {
Image: require('Image'), Image: require('Image'),
ListView: require('ListView'), ListView: require('ListView'),
MapView: require('MapView'), MapView: require('MapView'),
Navigator: require('Navigator'),
NavigatorIOS: require('NavigatorIOS'), NavigatorIOS: require('NavigatorIOS'),
PickerIOS: require('PickerIOS'), PickerIOS: require('PickerIOS'),
Navigator: require('Navigator'), ProgressViewIOS: require('ProgressViewIOS'),
SegmentedControlIOS: require('SegmentedControlIOS'),
ScrollView: require('ScrollView'), ScrollView: require('ScrollView'),
SegmentedControlIOS: require('SegmentedControlIOS'),
SliderIOS: require('SliderIOS'), SliderIOS: require('SliderIOS'),
SwitchIOS: require('SwitchIOS'), SwitchIOS: require('SwitchIOS'),
TabBarIOS: require('TabBarIOS'), TabBarIOS: require('TabBarIOS'),
@ -47,12 +48,12 @@ var ReactNative = Object.assign(Object.create(require('React')), {
AsyncStorage: require('AsyncStorage'), AsyncStorage: require('AsyncStorage'),
CameraRoll: require('CameraRoll'), CameraRoll: require('CameraRoll'),
InteractionManager: require('InteractionManager'), InteractionManager: require('InteractionManager'),
LinkingIOS: require('LinkingIOS'),
LayoutAnimation: require('LayoutAnimation'), LayoutAnimation: require('LayoutAnimation'),
LinkingIOS: require('LinkingIOS'),
NetInfo: require('NetInfo'), NetInfo: require('NetInfo'),
PanResponder: require('PanResponder'),
PixelRatio: require('PixelRatio'), PixelRatio: require('PixelRatio'),
PushNotificationIOS: require('PushNotificationIOS'), PushNotificationIOS: require('PushNotificationIOS'),
PanResponder: require('PanResponder'),
StatusBarIOS: require('StatusBarIOS'), StatusBarIOS: require('StatusBarIOS'),
StyleSheet: require('StyleSheet'), StyleSheet: require('StyleSheet'),
VibrationIOS: require('VibrationIOS'), VibrationIOS: require('VibrationIOS'),

View File

@ -232,6 +232,8 @@ var PRESS_EXPAND_PX = 20;
var LONG_PRESS_THRESHOLD = 500; var LONG_PRESS_THRESHOLD = 500;
var LONG_PRESS_DELAY_MS = LONG_PRESS_THRESHOLD - HIGHLIGHT_DELAY_MS;
var LONG_PRESS_ALLOWED_MOVEMENT = 10; var LONG_PRESS_ALLOWED_MOVEMENT = 10;
// Default amount "active" region protrudes beyond box // Default amount "active" region protrudes beyond box
@ -276,7 +278,7 @@ var LONG_PRESS_ALLOWED_MOVEMENT = 10;
* + * +
* | RESPONDER_GRANT (HitRect) * | RESPONDER_GRANT (HitRect)
* v * v
* +---------------------------+ DELAY +-------------------------+ T - DELAY +------------------------------+ * +---------------------------+ DELAY +-------------------------+ T + DELAY +------------------------------+
* |RESPONDER_INACTIVE_PRESS_IN|+-------->|RESPONDER_ACTIVE_PRESS_IN| +------------> |RESPONDER_ACTIVE_LONG_PRESS_IN| * |RESPONDER_INACTIVE_PRESS_IN|+-------->|RESPONDER_ACTIVE_PRESS_IN| +------------> |RESPONDER_ACTIVE_LONG_PRESS_IN|
* +---------------------------+ +-------------------------+ +------------------------------+ * +---------------------------+ +-------------------------+ +------------------------------+
* + ^ + ^ + ^ * + ^ + ^ + ^
@ -288,7 +290,7 @@ var LONG_PRESS_ALLOWED_MOVEMENT = 10;
* |RESPONDER_INACTIVE_PRESS_OUT|+------->|RESPONDER_ACTIVE_PRESS_OUT| |RESPONDER_ACTIVE_LONG_PRESS_OUT| * |RESPONDER_INACTIVE_PRESS_OUT|+------->|RESPONDER_ACTIVE_PRESS_OUT| |RESPONDER_ACTIVE_LONG_PRESS_OUT|
* +----------------------------+ +--------------------------+ +-------------------------------+ * +----------------------------+ +--------------------------+ +-------------------------------+
* *
* T - DELAY => LONG_PRESS_THRESHOLD - DELAY * T + DELAY => LONG_PRESS_DELAY_MS + DELAY
* *
* Not drawn are the side effects of each transition. The most important side * Not drawn are the side effects of each transition. The most important side
* effect is the `touchableHandlePress` abstract method invocation that occurs * effect is the `touchableHandlePress` abstract method invocation that occurs
@ -348,12 +350,16 @@ var TouchableMixin = {
// event to make sure it doesn't get reused in the event object pool. // event to make sure it doesn't get reused in the event object pool.
e.persist(); e.persist();
this.pressOutDelayTimeout && clearTimeout(this.pressOutDelayTimeout);
this.pressOutDelayTimeout = null;
this.state.touchable.touchState = States.NOT_RESPONDER; this.state.touchable.touchState = States.NOT_RESPONDER;
this.state.touchable.responderID = dispatchID; this.state.touchable.responderID = dispatchID;
this._receiveSignal(Signals.RESPONDER_GRANT, e); this._receiveSignal(Signals.RESPONDER_GRANT, e);
var delayMS = var delayMS =
this.touchableGetHighlightDelayMS !== undefined ? this.touchableGetHighlightDelayMS !== undefined ?
this.touchableGetHighlightDelayMS() : HIGHLIGHT_DELAY_MS; Math.max(this.touchableGetHighlightDelayMS(), 0) : HIGHLIGHT_DELAY_MS;
delayMS = isNaN(delayMS) ? HIGHLIGHT_DELAY_MS : delayMS;
if (delayMS !== 0) { if (delayMS !== 0) {
this.touchableDelayTimeout = setTimeout( this.touchableDelayTimeout = setTimeout(
this._handleDelay.bind(this, e), this._handleDelay.bind(this, e),
@ -363,9 +369,13 @@ var TouchableMixin = {
this._handleDelay(e); this._handleDelay(e);
} }
var longDelayMS =
this.touchableGetLongPressDelayMS !== undefined ?
Math.max(this.touchableGetLongPressDelayMS(), 10) : LONG_PRESS_DELAY_MS;
longDelayMS = isNaN(longDelayMS) ? LONG_PRESS_DELAY_MS : longDelayMS;
this.longPressDelayTimeout = setTimeout( this.longPressDelayTimeout = setTimeout(
this._handleLongDelay.bind(this, e), this._handleLongDelay.bind(this, e),
LONG_PRESS_THRESHOLD - delayMS longDelayMS + delayMS
); );
}, },
@ -632,8 +642,14 @@ var TouchableMixin = {
if (newIsHighlight && !curIsHighlight) { if (newIsHighlight && !curIsHighlight) {
this._savePressInLocation(e); this._savePressInLocation(e);
this.touchableHandleActivePressIn && this.touchableHandleActivePressIn(); this.touchableHandleActivePressIn && this.touchableHandleActivePressIn();
} else if (!newIsHighlight && curIsHighlight) { } else if (!newIsHighlight && curIsHighlight && this.touchableHandleActivePressOut) {
this.touchableHandleActivePressOut && this.touchableHandleActivePressOut(); if (this.touchableGetPressOutDelayMS && this.touchableGetPressOutDelayMS()) {
this.pressOutDelayTimeout = this.setTimeout(function() {
this.touchableHandleActivePressOut();
}, this.touchableGetPressOutDelayMS());
} else {
this.touchableHandleActivePressOut();
}
} }
if (IsPressingIn[curState] && signal === Signals.RESPONDER_RELEASE) { if (IsPressingIn[curState] && signal === Signals.RESPONDER_RELEASE) {

View File

@ -244,30 +244,19 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
@implementation RCTModuleMethod @implementation RCTModuleMethod
{ {
BOOL _isClassMethod;
Class _moduleClass; Class _moduleClass;
SEL _selector; SEL _selector;
NSMethodSignature *_methodSignature; NSMethodSignature *_methodSignature;
NSArray *_argumentBlocks; NSArray *_argumentBlocks;
NSString *_methodName;
dispatch_block_t _methodQueue; dispatch_block_t _methodQueue;
} }
static NSString *RCTStringUpToFirstArgument(NSString *methodName)
{
NSRange colonRange = [methodName rangeOfString:@":"];
if (colonRange.length) {
methodName = [methodName substringToIndex:colonRange.location];
}
return methodName;
}
- (instancetype)initWithReactMethodName:(NSString *)reactMethodName - (instancetype)initWithReactMethodName:(NSString *)reactMethodName
objCMethodName:(NSString *)objCMethodName objCMethodName:(NSString *)objCMethodName
JSMethodName:(NSString *)JSMethodName JSMethodName:(NSString *)JSMethodName
{ {
if ((self = [super init])) { if ((self = [super init])) {
_methodName = reactMethodName;
NSArray *parts = [[reactMethodName substringWithRange:(NSRange){2, reactMethodName.length - 3}] componentsSeparatedByString:@" "]; NSArray *parts = [[reactMethodName substringWithRange:(NSRange){2, reactMethodName.length - 3}] componentsSeparatedByString:@" "];
// Parse class and method // Parse class and method
@ -277,12 +266,16 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
_moduleClassName = [_moduleClassName substringToIndex:categoryRange.location]; _moduleClassName = [_moduleClassName substringToIndex:categoryRange.location];
} }
NSArray *argumentNames = nil;
if ([parts[1] hasPrefix:@"__rct_export__"]) {
// New format
NSString *selectorString = [parts[1] substringFromIndex:14]; NSString *selectorString = [parts[1] substringFromIndex:14];
_selector = NSSelectorFromString(selectorString); _selector = NSSelectorFromString(selectorString);
_JSMethodName = JSMethodName ?: RCTStringUpToFirstArgument(selectorString); _JSMethodName = JSMethodName ?: ({
NSString *methodName = selectorString;
NSRange colonRange = [methodName rangeOfString:@":"];
if (colonRange.length) {
methodName = [methodName substringToIndex:colonRange.location];
}
methodName;
});
static NSRegularExpression *regExp; static NSRegularExpression *regExp;
if (!regExp) { if (!regExp) {
@ -293,20 +286,13 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
regExp = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL]; regExp = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL];
} }
argumentNames = [NSMutableArray array]; NSMutableArray *argumentNames = [NSMutableArray array];
[regExp enumerateMatchesInString:objCMethodName options:0 range:NSMakeRange(0, objCMethodName.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { [regExp enumerateMatchesInString:objCMethodName options:0 range:NSMakeRange(0, objCMethodName.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSString *argumentName = [objCMethodName substringWithRange:[result rangeAtIndex:1]]; NSString *argumentName = [objCMethodName substringWithRange:[result rangeAtIndex:1]];
[(NSMutableArray *)argumentNames addObject:argumentName]; [argumentNames addObject:argumentName];
}]; }];
} else {
// Old format
NSString *selectorString = parts[1];
_selector = NSSelectorFromString(selectorString);
_JSMethodName = JSMethodName ?: RCTStringUpToFirstArgument(selectorString);
}
// Extract class and method details // Extract class and method details
_isClassMethod = [reactMethodName characterAtIndex:0] == '+';
_moduleClass = NSClassFromString(_moduleClassName); _moduleClass = NSClassFromString(_moduleClassName);
if (RCT_DEBUG) { if (RCT_DEBUG) {
@ -318,9 +304,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
} }
// Get method signature // Get method signature
_methodSignature = _isClassMethod ? _methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
[_moduleClass methodSignatureForSelector:_selector] :
[_moduleClass instanceMethodSignatureForSelector:_selector];
// Process arguments // Process arguments
NSUInteger numberOfArguments = _methodSignature.numberOfArguments; NSUInteger numberOfArguments = _methodSignature.numberOfArguments;
@ -363,12 +347,9 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
for (NSUInteger i = 2; i < numberOfArguments; i++) { for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *argumentType = [_methodSignature getArgumentTypeAtIndex:i]; const char *argumentType = [_methodSignature getArgumentTypeAtIndex:i];
BOOL useFallback = YES;
if (argumentNames) {
NSString *argumentName = argumentNames[i - 2]; NSString *argumentName = argumentNames[i - 2];
SEL selector = NSSelectorFromString([argumentName stringByAppendingString:@":"]); SEL selector = NSSelectorFromString([argumentName stringByAppendingString:@":"]);
if ([RCTConvert respondsToSelector:selector]) { if ([RCTConvert respondsToSelector:selector]) {
useFallback = NO;
switch (argumentType[0]) { switch (argumentType[0]) {
#define RCT_CONVERT_CASE(_value, _type) \ #define RCT_CONVERT_CASE(_value, _type) \
@ -419,63 +400,11 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
} }
} else if ([argumentName isEqualToString:@"RCTResponseSenderBlock"]) { } else if ([argumentName isEqualToString:@"RCTResponseSenderBlock"]) {
addBlockArgument(); addBlockArgument();
useFallback = NO; } else {
}
}
if (useFallback) { // Unknown argument type
switch (argumentType[0]) { RCTLogError(@"Unknown argument type '%@' in method %@. Extend RCTConvert"
" to support this type.", argumentName, [self methodName]);
#define RCT_CASE(_value, _class, _logic) \
case _value: { \
RCT_ARG_BLOCK( \
if (RCT_DEBUG && json && ![json isKindOfClass:[_class class]]) { \
RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
return; \
} \
_logic \
) \
break; \
}
RCT_CASE(':', NSString, SEL value = NSSelectorFromString(json); )
RCT_CASE('*', NSString, const char *value = [json UTF8String]; )
#define RCT_SIMPLE_CASE(_value, _type, _selector) \
case _value: { \
RCT_ARG_BLOCK( \
if (RCT_DEBUG && json && ![json respondsToSelector:@selector(_selector)]) { \
RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \
index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
return; \
} \
_type value = [json _selector]; \
) \
break; \
}
RCT_SIMPLE_CASE('c', char, charValue)
RCT_SIMPLE_CASE('C', unsigned char, unsignedCharValue)
RCT_SIMPLE_CASE('s', short, shortValue)
RCT_SIMPLE_CASE('S', unsigned short, unsignedShortValue)
RCT_SIMPLE_CASE('i', int, intValue)
RCT_SIMPLE_CASE('I', unsigned int, unsignedIntValue)
RCT_SIMPLE_CASE('l', long, longValue)
RCT_SIMPLE_CASE('L', unsigned long, unsignedLongValue)
RCT_SIMPLE_CASE('q', long long, longLongValue)
RCT_SIMPLE_CASE('Q', unsigned long long, unsignedLongLongValue)
RCT_SIMPLE_CASE('f', float, floatValue)
RCT_SIMPLE_CASE('d', double, doubleValue)
RCT_SIMPLE_CASE('B', BOOL, boolValue)
case '{':
RCTLogMustFix(@"Cannot convert JSON to struct %s", argumentType);
break;
default:
defaultCase(argumentType);
}
} }
} }
@ -494,7 +423,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
// Sanity check // Sanity check
RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \ RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
%@ on a module of class %@", _methodName, [module class]); %@ on a module of class %@", [self methodName], [module class]);
// Safety check // Safety check
if (arguments.count != _argumentBlocks.count) { if (arguments.count != _argumentBlocks.count) {
@ -520,12 +449,19 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
} }
// Invoke method // Invoke method
[invocation invokeWithTarget:_isClassMethod ? [module class] : module]; [invocation invokeWithTarget:module];
}
- (NSString *)methodName
{
return [NSString stringWithFormat:@"-[%@ %@]", _moduleClass,
NSStringFromSelector(_selector)];
} }
- (NSString *)description - (NSString *)description
{ {
return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@;>", NSStringFromClass(self.class), self, _methodName, _JSMethodName]; return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@();>",
[self class], self, [self methodName], _JSMethodName];
} }
@end @end
@ -562,19 +498,10 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void)
const char **entries = (const char **)(mach_header + addr); const char **entries = (const char **)(mach_header + addr);
// Create method // Create method
RCTModuleMethod *moduleMethod; RCTModuleMethod *moduleMethod =
if (entries[2] == NULL) { [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0])
objCMethodName:@(entries[1])
// Legacy support for RCT_EXPORT()
moduleMethod = [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0])
objCMethodName:@(entries[0])
JSMethodName:strlen(entries[1]) ? @(entries[1]) : nil];
} else {
moduleMethod = [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0])
objCMethodName:strlen(entries[1]) ? @(entries[1]) : nil
JSMethodName:strlen(entries[2]) ? @(entries[2]) : nil]; JSMethodName:strlen(entries[2]) ? @(entries[2]) : nil];
}
// Cache method // Cache method
NSArray *methods = methodsByModuleClassName[moduleMethod.moduleClassName]; NSArray *methods = methodsByModuleClassName[moduleMethod.moduleClassName];
methodsByModuleClassName[moduleMethod.moduleClassName] = methodsByModuleClassName[moduleMethod.moduleClassName] =
@ -1313,7 +1240,12 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
#pragma mark - Payload Generation #pragma mark - Payload Generation
- (void)dispatchBlock:(dispatch_block_t)block forModule:(NSNumber *)moduleID - (void)dispatchBlock:(dispatch_block_t)block forModule:(id<RCTBridgeModule>)module
{
[self dispatchBlock:block forModuleID:RCTModuleIDsByName[RCTBridgeModuleNameForClass([module class])]];
}
- (void)dispatchBlock:(dispatch_block_t)block forModuleID:(NSNumber *)moduleID
{ {
RCTAssertJSThread(); RCTAssertJSThread();
@ -1458,7 +1390,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
if ([module respondsToSelector:@selector(batchDidComplete)]) { if ([module respondsToSelector:@selector(batchDidComplete)]) {
[self dispatchBlock:^{ [self dispatchBlock:^{
[module batchDidComplete]; [module batchDidComplete];
} forModule:moduleID]; } forModuleID:moduleID];
} }
}]; }];
} }
@ -1526,7 +1458,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
@"selector": NSStringFromSelector(method.selector), @"selector": NSStringFromSelector(method.selector),
@"args": RCTJSONStringify(params ?: [NSNull null], NULL), @"args": RCTJSONStringify(params ?: [NSNull null], NULL),
}); });
} forModule:@(moduleID)]; } forModuleID:@(moduleID)];
return YES; return YES;
} }
@ -1546,7 +1478,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
RCTProfileBeginEvent(); RCTProfileBeginEvent();
[observer didUpdateFrame:frameUpdate]; [observer didUpdateFrame:frameUpdate];
RCTProfileEndEvent(name, @"objc_call,fps", nil); RCTProfileEndEvent(name, @"objc_call,fps", nil);
} forModule:RCTModuleIDsByName[RCTBridgeModuleNameForClass([observer class])]]; } forModule:(id<RCTBridgeModule>)observer];
} }
} }
@ -1598,14 +1530,17 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
return; return;
} }
RCTProfileInit(); [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
RCTProfileInit(self);
}];
} }
- (void)stopProfiling - (void)stopProfiling
{ {
RCTAssertMainThread(); RCTAssertMainThread();
NSString *log = RCTProfileEnd(); [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
NSString *log = RCTProfileEnd(self);
NSURL *bundleURL = _parentBridge.bundleURL; NSURL *bundleURL = _parentBridge.bundleURL;
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port]; NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port];
NSURL *URL = [NSURL URLWithString:URLString]; NSURL *URL = [NSURL URLWithString:URLString];
@ -1620,6 +1555,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
} }
}]; }];
[task resume]; [task resume];
}];
} }
@end @end

View File

@ -145,15 +145,6 @@ extern const dispatch_queue_t RCTJSThread;
static const char *__rct_export_entry__[] = { __func__, #method, #js_name }; \ static const char *__rct_export_entry__[] = { __func__, #method, #js_name }; \
} }
/**
* Deprecated, do not use.
*/
#define RCT_EXPORT(js_name) \
_Pragma("message(\"RCT_EXPORT is deprecated. Use RCT_EXPORT_METHOD instead.\")") \
__attribute__((used, section("__DATA,RCTExport"))) \
__attribute__((__aligned__(1))) \
static const char *__rct_export_entry__[] = { __func__, #js_name, NULL }
/** /**
* The queue that will be used to call all exported methods. If omitted, this * The queue that will be used to call all exported methods. If omitted, this
* will call on the default background queue, which is avoids blocking the main * will call on the default background queue, which is avoids blocking the main

View File

@ -23,6 +23,8 @@
*/ */
@interface RCTConvert : NSObject @interface RCTConvert : NSObject
+ (id)id:(id)json;
+ (BOOL)BOOL:(id)json; + (BOOL)BOOL:(id)json;
+ (double)double:(id)json; + (double)double:(id)json;
+ (float)float:(id)json; + (float)float:(id)json;
@ -52,7 +54,6 @@
+ (NSWritingDirection)NSWritingDirection:(id)json; + (NSWritingDirection)NSWritingDirection:(id)json;
+ (UITextAutocapitalizationType)UITextAutocapitalizationType:(id)json; + (UITextAutocapitalizationType)UITextAutocapitalizationType:(id)json;
+ (UITextFieldViewMode)UITextFieldViewMode:(id)json; + (UITextFieldViewMode)UITextFieldViewMode:(id)json;
+ (UIScrollViewKeyboardDismissMode)UIScrollViewKeyboardDismissMode:(id)json;
+ (UIKeyboardType)UIKeyboardType:(id)json; + (UIKeyboardType)UIKeyboardType:(id)json;
+ (UIReturnKeyType)UIReturnKeyType:(id)json; + (UIReturnKeyType)UIReturnKeyType:(id)json;

View File

@ -21,6 +21,8 @@ void RCTLogConvertError(id json, const char *type)
json, [json classForCoder], type); json, [json classForCoder], type);
} }
RCT_CONVERTER(id, id, self)
RCT_CONVERTER(BOOL, BOOL, boolValue) RCT_CONVERTER(BOOL, BOOL, boolValue)
RCT_NUMBER_CONVERTER(double, doubleValue) RCT_NUMBER_CONVERTER(double, doubleValue)
RCT_NUMBER_CONVERTER(float, floatValue) RCT_NUMBER_CONVERTER(float, floatValue)
@ -219,12 +221,6 @@ RCT_ENUM_CONVERTER(UITextFieldViewMode, (@{
@"always": @(UITextFieldViewModeAlways), @"always": @(UITextFieldViewModeAlways),
}), UITextFieldViewModeNever, integerValue) }), UITextFieldViewModeNever, integerValue)
RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{
@"none": @(UIScrollViewKeyboardDismissModeNone),
@"on-drag": @(UIScrollViewKeyboardDismissModeOnDrag),
@"interactive": @(UIScrollViewKeyboardDismissModeInteractive),
}), UIScrollViewKeyboardDismissModeNone, integerValue)
RCT_ENUM_CONVERTER(UIKeyboardType, (@{ RCT_ENUM_CONVERTER(UIKeyboardType, (@{
@"default": @(UIKeyboardTypeDefault), @"default": @(UIKeyboardTypeDefault),
@"ascii-capable": @(UIKeyboardTypeASCIICapable), @"ascii-capable": @(UIKeyboardTypeASCIICapable),
@ -237,6 +233,8 @@ RCT_ENUM_CONVERTER(UIKeyboardType, (@{
@"decimal-pad": @(UIKeyboardTypeDecimalPad), @"decimal-pad": @(UIKeyboardTypeDecimalPad),
@"twitter": @(UIKeyboardTypeTwitter), @"twitter": @(UIKeyboardTypeTwitter),
@"web-search": @(UIKeyboardTypeWebSearch), @"web-search": @(UIKeyboardTypeWebSearch),
// Added for Android compatibility
@"numeric": @(UIKeyboardTypeDecimalPad),
}), UIKeyboardTypeDefault, integerValue) }), UIKeyboardTypeDefault, integerValue)
RCT_ENUM_CONVERTER(UIReturnKeyType, (@{ RCT_ENUM_CONVERTER(UIReturnKeyType, (@{
@ -267,7 +265,11 @@ RCT_ENUM_CONVERTER(UIViewContentMode, (@{
@"top-right": @(UIViewContentModeTopRight), @"top-right": @(UIViewContentModeTopRight),
@"bottom-left": @(UIViewContentModeBottomLeft), @"bottom-left": @(UIViewContentModeBottomLeft),
@"bottom-right": @(UIViewContentModeBottomRight), @"bottom-right": @(UIViewContentModeBottomRight),
}), UIViewContentModeScaleToFill, integerValue) // Cross-platform values
@"cover": @(UIViewContentModeScaleAspectFill),
@"contain": @(UIViewContentModeScaleAspectFit),
@"stretch": @(UIViewContentModeScaleToFill),
}), UIViewContentModeScaleAspectFill, integerValue)
RCT_ENUM_CONVERTER(UIBarStyle, (@{ RCT_ENUM_CONVERTER(UIBarStyle, (@{
@"default": @(UIBarStyleDefault), @"default": @(UIBarStyleDefault),

View File

@ -51,6 +51,12 @@
*/ */
- (void)reload; - (void)reload;
/**
* Add custom item to the development menu. The handler will be called
* when user selects the item.
*/
- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler;
@end @end
/** /**

View File

@ -43,6 +43,28 @@ static NSString *const RCTDevMenuSettingsKey = @"RCTDevMenu";
@end @end
@interface RCTDevMenuItem : NSObject
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) dispatch_block_t handler;
- (instancetype)initWithTitle:(NSString *)title handler:(dispatch_block_t)handler;
@end
@implementation RCTDevMenuItem
- (instancetype)initWithTitle:(NSString *)title handler:(dispatch_block_t)handler
{
if (self = [super init]) {
self.title = title;
self.handler = handler;
}
return self;
}
@end
@interface RCTDevMenu () <RCTBridgeModule, UIActionSheetDelegate> @interface RCTDevMenu () <RCTBridgeModule, UIActionSheetDelegate>
@property (nonatomic, strong) Class executorClass; @property (nonatomic, strong) Class executorClass;
@ -57,6 +79,8 @@ static NSString *const RCTDevMenuSettingsKey = @"RCTDevMenu";
NSURLSessionDataTask *_updateTask; NSURLSessionDataTask *_updateTask;
NSURL *_liveReloadURL; NSURL *_liveReloadURL;
BOOL _jsLoaded; BOOL _jsLoaded;
NSArray *_presentedItems;
NSMutableArray *_extraMenuItems;
} }
@synthesize bridge = _bridge; @synthesize bridge = _bridge;
@ -94,6 +118,7 @@ RCT_EXPORT_MODULE()
_defaults = [NSUserDefaults standardUserDefaults]; _defaults = [NSUserDefaults standardUserDefaults];
_settings = [[NSMutableDictionary alloc] init]; _settings = [[NSMutableDictionary alloc] init];
_extraMenuItems = [NSMutableArray array];
// Delay setup until after Bridge init // Delay setup until after Bridge init
[self settingsDidChange]; [self settingsDidChange];
@ -110,6 +135,13 @@ RCT_EXPORT_MODULE()
[weakSelf toggle]; [weakSelf toggle];
}]; }];
// Toggle element inspector
[commands registerKeyCommandWithInput:@"i"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
[_bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil];
}];
// Reload in normal mode // Reload in normal mode
[commands registerKeyCommandWithInput:@"n" [commands registerKeyCommandWithInput:@"n"
modifierFlags:UIKeyModifierCommand modifierFlags:UIKeyModifierCommand
@ -225,32 +257,82 @@ RCT_EXPORT_MODULE()
} }
} }
- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler
{
[_extraMenuItems addObject:[[RCTDevMenuItem alloc] initWithTitle:title handler:handler]];
}
- (NSArray *)menuItems
{
NSMutableArray *items = [NSMutableArray array];
[items addObject:[[RCTDevMenuItem alloc] initWithTitle:@"Reload" handler:^{
[self reload];
}]];
Class chromeExecutorClass = NSClassFromString(@"RCTWebSocketExecutor");
if (!chromeExecutorClass) {
[items addObject:[[RCTDevMenuItem alloc] initWithTitle:@"Chrome Debugger Unavailable" handler:^{
[[[UIAlertView alloc] initWithTitle:@"Chrome Debugger Unavailable"
message:@"You need to include the RCTWebSocket library to enable Chrome debugging"
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
}]];
} else {
BOOL isDebuggingInChrome = _executorClass && _executorClass == chromeExecutorClass;
NSString *debugTitleChrome = isDebuggingInChrome ? @"Disable Chrome Debugging" : @"Debug in Chrome";
[items addObject:[[RCTDevMenuItem alloc] initWithTitle:debugTitleChrome handler:^{
self.executorClass = isDebuggingInChrome ? Nil : chromeExecutorClass;
}]];
}
Class safariExecutorClass = NSClassFromString(@"RCTWebViewExecutor");
BOOL isDebuggingInSafari = _executorClass && _executorClass == safariExecutorClass;
NSString *debugTitleSafari = isDebuggingInSafari ? @"Disable Safari Debugging" : @"Debug in Safari";
[items addObject:[[RCTDevMenuItem alloc] initWithTitle:debugTitleSafari handler:^{
self.executorClass = isDebuggingInSafari ? Nil : safariExecutorClass;
}]];
NSString *fpsMonitor = _showFPS ? @"Hide FPS Monitor" : @"Show FPS Monitor";
[items addObject:[[RCTDevMenuItem alloc] initWithTitle:fpsMonitor handler:^{
self.showFPS = !_showFPS;
}]];
[items addObject:[[RCTDevMenuItem alloc] initWithTitle:@"Inspect Element" handler:^{
[_bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil];
}]];
if (_liveReloadURL) {
NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload";
[items addObject:[[RCTDevMenuItem alloc] initWithTitle:liveReloadTitle handler:^{
self.liveReloadEnabled = !_liveReloadEnabled;
}]];
NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling";
[items addObject:[[RCTDevMenuItem alloc] initWithTitle:profilingTitle handler:^{
self.profilingEnabled = !_profilingEnabled;
}]];
}
[items addObjectsFromArray:_extraMenuItems];
return items;
}
RCT_EXPORT_METHOD(show) RCT_EXPORT_METHOD(show)
{ {
if (_actionSheet || !_bridge) { if (_actionSheet || !_bridge) {
return; return;
} }
NSString *debugTitleChrome = _executorClass && _executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Debug in Chrome"; UIActionSheet *actionSheet = [[UIActionSheet alloc] init];
NSString *debugTitleSafari = _executorClass && _executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Debug in Safari"; actionSheet.title = @"React Native: Development";
NSString *fpsMonitor = _showFPS ? @"Hide FPS Monitor" : @"Show FPS Monitor"; actionSheet.delegate = self;
UIActionSheet *actionSheet = NSArray *items = [self menuItems];
[[UIActionSheet alloc] initWithTitle:@"React Native: Development" for (RCTDevMenuItem *item in items) {
delegate:self [actionSheet addButtonWithTitle:item.title];
cancelButtonTitle:nil
destructiveButtonTitle:nil
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, fpsMonitor, nil];
[actionSheet addButtonWithTitle:@"Inspect Element"];
if (_liveReloadURL) {
NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload";
NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling";
[actionSheet addButtonWithTitle:liveReloadTitle];
[actionSheet addButtonWithTitle:profilingTitle];
} }
[actionSheet addButtonWithTitle:@"Cancel"]; [actionSheet addButtonWithTitle:@"Cancel"];
@ -259,13 +341,7 @@ RCT_EXPORT_METHOD(show)
actionSheet.actionSheetStyle = UIBarStyleBlack; actionSheet.actionSheetStyle = UIBarStyleBlack;
[actionSheet showInView:[UIApplication sharedApplication].keyWindow.rootViewController.view]; [actionSheet showInView:[UIApplication sharedApplication].keyWindow.rootViewController.view];
_actionSheet = actionSheet; _actionSheet = actionSheet;
} _presentedItems = items;
RCT_EXPORT_METHOD(reload)
{
_jsLoaded = NO;
_liveReloadURL = nil;
[_bridge reload];
} }
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
@ -275,48 +351,16 @@ RCT_EXPORT_METHOD(reload)
return; return;
} }
switch (buttonIndex) { RCTDevMenuItem *item = _presentedItems[buttonIndex];
case 0: { item.handler();
[self reload];
break;
}
case 1: {
Class cls = NSClassFromString(@"RCTWebSocketExecutor");
if (!cls) {
[[[UIAlertView alloc] initWithTitle:@"Chrome Debugger Unavailable"
message:@"You need to include the RCTWebSocket library to enable Chrome debugging"
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
return; return;
} }
self.executorClass = (_executorClass == cls) ? Nil : cls;
break; RCT_EXPORT_METHOD(reload)
} {
case 2: { _jsLoaded = NO;
Class cls = NSClassFromString(@"RCTWebViewExecutor"); _liveReloadURL = nil;
self.executorClass = (_executorClass == cls) ? Nil : cls; [_bridge reload];
break;
}
case 3: {
self.showFPS = !_showFPS;
break;
}
case 4: {
[_bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil];
break;
}
case 5: {
self.liveReloadEnabled = !_liveReloadEnabled;
break;
}
case 6: {
self.profilingEnabled = !_profilingEnabled;
break;
}
default:
break;
}
} }
- (void)setShakeToShow:(BOOL)shakeToShow - (void)setShakeToShow:(BOOL)shakeToShow
@ -438,6 +482,7 @@ RCT_EXPORT_METHOD(reload)
- (void)show {} - (void)show {}
- (void)reload {} - (void)reload {}
- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler {}
@end @end

View File

@ -25,6 +25,8 @@ NSString *const RCTProfileDidEndProfiling;
#if RCT_DEV #if RCT_DEV
@class RCTBridge;
#define RCTProfileBeginFlowEvent() \ #define RCTProfileBeginFlowEvent() \
_Pragma("clang diagnostic push") \ _Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \ _Pragma("clang diagnostic ignored \"-Wshadow\"") \
@ -45,14 +47,14 @@ RCT_EXTERN BOOL RCTProfileIsProfiling(void);
/** /**
* Start collecting profiling information * Start collecting profiling information
*/ */
RCT_EXTERN void RCTProfileInit(void); RCT_EXTERN void RCTProfileInit(RCTBridge *);
/** /**
* Stop profiling and return a JSON string of the collected data - The data * Stop profiling and return a JSON string of the collected data - The data
* returned is compliant with google's trace event format - the format used * returned is compliant with google's trace event format - the format used
* as input to trace-viewer * as input to trace-viewer
*/ */
RCT_EXTERN NSString *RCTProfileEnd(void); RCT_EXTERN NSString *RCTProfileEnd(RCTBridge *);
/** /**
* Collects the initial event information for the event and returns a reference ID * Collects the initial event information for the event and returns a reference ID

View File

@ -10,10 +10,13 @@
#import "RCTProfile.h" #import "RCTProfile.h"
#import <mach/mach.h> #import <mach/mach.h>
#import <objc/message.h>
#import <objc/runtime.h>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "RCTAssert.h" #import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTDefines.h" #import "RCTDefines.h"
#import "RCTUtils.h" #import "RCTUtils.h"
@ -32,6 +35,7 @@ NSDictionary *RCTProfileGetMemoryUsage(void);
NSString const *RCTProfileTraceEvents = @"traceEvents"; NSString const *RCTProfileTraceEvents = @"traceEvents";
NSString const *RCTProfileSamples = @"samples"; NSString const *RCTProfileSamples = @"samples";
NSString *const RCTProfilePrefix = @"rct_profile_";
#pragma mark - Variables #pragma mark - Variables
@ -92,6 +96,111 @@ NSDictionary *RCTProfileGetMemoryUsage(void)
} }
} }
#pragma mark - Module hooks
@interface RCTBridge (Private)
- (void)dispatchBlock:(dispatch_block_t)block forModule:(id<RCTBridgeModule>)module;
@end
static const char *RCTProfileProxyClassName(Class);
static const char *RCTProfileProxyClassName(Class class)
{
return [RCTProfilePrefix stringByAppendingString:NSStringFromClass(class)].UTF8String;
}
static SEL RCTProfileProxySelector(SEL);
static SEL RCTProfileProxySelector(SEL selector)
{
NSString *selectorName = NSStringFromSelector(selector);
return NSSelectorFromString([RCTProfilePrefix stringByAppendingString:selectorName]);
}
static void RCTProfileForwardInvocation(NSObject *, SEL, NSInvocation *);
static void RCTProfileForwardInvocation(NSObject *self, SEL cmd, NSInvocation *invocation)
{
NSString *name = [NSString stringWithFormat:@"-[%@ %@]", NSStringFromClass([self class]), NSStringFromSelector(invocation.selector)];
SEL newSel = RCTProfileProxySelector(invocation.selector);
if ([object_getClass(self) instancesRespondToSelector:newSel]) {
invocation.selector = newSel;
RCTProfileBeginEvent();
[invocation invoke];
RCTProfileEndEvent(name, @"objc_call,modules,auto", nil);
} else {
// Use original selector to don't change error message
[self doesNotRecognizeSelector:invocation.selector];
}
}
static IMP RCTProfileMsgForward(NSObject *, SEL);
static IMP RCTProfileMsgForward(NSObject *self, SEL selector)
{
IMP imp = _objc_msgForward;
#if !defined(__arm64__)
NSMethodSignature *signature = [self methodSignatureForSelector:selector];
if (signature.methodReturnType[0] == _C_STRUCT_B && signature.methodReturnLength > 8) {
imp = _objc_msgForward_stret;
}
#endif
return imp;
}
static void RCTProfileHookModules(RCTBridge *);
static void RCTProfileHookModules(RCTBridge *bridge)
{
[bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *className, id<RCTBridgeModule> module, BOOL *stop) {
[bridge dispatchBlock:^{
Class moduleClass = object_getClass(module);
Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0);
unsigned int methodCount;
Method *methods = class_copyMethodList(moduleClass, &methodCount);
for (NSUInteger i = 0; i < methodCount; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
if ([NSStringFromSelector(selector) hasPrefix:@"rct"] || [NSObject instancesRespondToSelector:selector]) {
continue;
}
IMP originalIMP = method_getImplementation(method);
const char *returnType = method_getTypeEncoding(method);
class_addMethod(proxyClass, selector, RCTProfileMsgForward(module, selector), returnType);
class_addMethod(proxyClass, RCTProfileProxySelector(selector), originalIMP, returnType);
}
free(methods);
for (Class cls in @[proxyClass, object_getClass(proxyClass)]) {
Method oldImp = class_getInstanceMethod(cls, @selector(class));
class_replaceMethod(cls, @selector(class), imp_implementationWithBlock(^{ return moduleClass; }), method_getTypeEncoding(oldImp));
}
IMP originalFwd = class_replaceMethod(moduleClass, @selector(forwardInvocation:), (IMP)RCTProfileForwardInvocation, "v@:@");
if (originalFwd != NULL) {
class_addMethod(proxyClass, RCTProfileProxySelector(@selector(forwardInvocation:)), originalFwd, "v@:@");
}
objc_registerClassPair(proxyClass);
object_setClass(module, proxyClass);
} forModule:module];
}];
}
void RCTProfileUnhookModules(RCTBridge *);
void RCTProfileUnhookModules(RCTBridge *bridge)
{
[bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *className, id<RCTBridgeModule> module, BOOL *stop) {
[bridge dispatchBlock:^{
Class proxyClass = object_getClass(module);
if (module.class != proxyClass) {
object_setClass(module, module.class);
objc_disposeClassPair(proxyClass);
}
} forModule:module];
}];
}
#pragma mark - Public Functions #pragma mark - Public Functions
BOOL RCTProfileIsProfiling(void) BOOL RCTProfileIsProfiling(void)
@ -102,8 +211,10 @@ BOOL RCTProfileIsProfiling(void)
return profiling; return profiling;
} }
void RCTProfileInit(void) void RCTProfileInit(RCTBridge *bridge)
{ {
RCTProfileHookModules(bridge);
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
_RCTProfileLock = [[NSLock alloc] init]; _RCTProfileLock = [[NSLock alloc] init];
@ -121,7 +232,7 @@ void RCTProfileInit(void)
object:nil]; object:nil];
} }
NSString *RCTProfileEnd(void) NSString *RCTProfileEnd(RCTBridge *bridge)
{ {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidEndProfiling [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidEndProfiling
object:nil]; object:nil];
@ -132,6 +243,9 @@ NSString *RCTProfileEnd(void)
RCTProfileInfo = nil; RCTProfileInfo = nil;
RCTProfileOngoingEvents = nil; RCTProfileOngoingEvents = nil;
); );
RCTProfileUnhookModules(bridge);
return log; return log;
} }

View File

@ -85,11 +85,6 @@
selector:@selector(dismiss) selector:@selector(dismiss)
name:RCTReloadNotification name:RCTReloadNotification
object:nil]; object:nil];
[notificationCenter addObserver:self
selector:@selector(dismiss)
name:RCTJavaScriptDidLoadNotification
object:nil];
} }
return self; return self;
} }

View File

@ -36,8 +36,6 @@
BOOL _recordingInteractionTiming; BOOL _recordingInteractionTiming;
CFTimeInterval _mostRecentEnqueueJS; CFTimeInterval _mostRecentEnqueueJS;
NSMutableArray *_pendingTouches;
NSMutableArray *_bridgeInteractionTiming;
} }
- (instancetype)initWithBridge:(RCTBridge *)bridge - (instancetype)initWithBridge:(RCTBridge *)bridge
@ -52,9 +50,6 @@
_reactTouches = [[NSMutableArray alloc] init]; _reactTouches = [[NSMutableArray alloc] init];
_touchViews = [[NSMutableArray alloc] init]; _touchViews = [[NSMutableArray alloc] init];
_pendingTouches = [[NSMutableArray alloc] init];
_bridgeInteractionTiming = [[NSMutableArray alloc] init];
// `cancelsTouchesInView` is needed in order to be used as a top level // `cancelsTouchesInView` is needed in order to be used as a top level
// event delegated recognizer. Otherwise, lower-level components not built // event delegated recognizer. Otherwise, lower-level components not built
// using RCT, will fail to recognize gestures. // using RCT, will fail to recognize gestures.
@ -94,11 +89,11 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) {
return; return;
} }
// Get new, unique touch id // Get new, unique touch identifier for the react touch
const NSUInteger RCTMaxTouches = 11; // This is the maximum supported by iDevices const NSUInteger RCTMaxTouches = 11; // This is the maximum supported by iDevices
NSInteger touchID = ([_reactTouches.lastObject[@"target"] integerValue] + 1) % RCTMaxTouches; NSInteger touchID = ([_reactTouches.lastObject[@"identifier"] integerValue] + 1) % RCTMaxTouches;
for (NSDictionary *reactTouch in _reactTouches) { for (NSDictionary *reactTouch in _reactTouches) {
NSInteger usedID = [reactTouch[@"target"] integerValue]; NSInteger usedID = [reactTouch[@"identifier"] integerValue];
if (usedID == touchID) { if (usedID == touchID) {
// ID has already been used, try next value // ID has already been used, try next value
touchID ++; touchID ++;

View File

@ -18,6 +18,8 @@
// Utility functions for JSON object <-> string serialization/deserialization // Utility functions for JSON object <-> string serialization/deserialization
RCT_EXTERN NSString *RCTJSONStringify(id jsonObject, NSError **error); RCT_EXTERN NSString *RCTJSONStringify(id jsonObject, NSError **error);
RCT_EXTERN id RCTJSONParse(NSString *jsonString, NSError **error); RCT_EXTERN id RCTJSONParse(NSString *jsonString, NSError **error);
RCT_EXTERN id RCTJSONParseMutable(NSString *jsonString, NSError **error);
RCT_EXTERN id RCTJSONParseWithOptions(NSString *jsonString, NSError **error, NSJSONReadingOptions options);
// Strip non JSON-safe values from an object graph // Strip non JSON-safe values from an object graph
RCT_EXTERN id RCTJSONClean(id object); RCT_EXTERN id RCTJSONClean(id object);

View File

@ -24,7 +24,7 @@ NSString *RCTJSONStringify(id jsonObject, NSError **error)
return jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : nil; return jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : nil;
} }
id RCTJSONParse(NSString *jsonString, NSError **error) id RCTJSONParseWithOptions(NSString *jsonString, NSError **error, NSJSONReadingOptions options)
{ {
if (!jsonString) { if (!jsonString) {
return nil; return nil;
@ -39,7 +39,15 @@ id RCTJSONParse(NSString *jsonString, NSError **error)
return nil; return nil;
} }
} }
return [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:error]; return [NSJSONSerialization JSONObjectWithData:jsonData options:options error:error];
}
id RCTJSONParse(NSString *jsonString, NSError **error) {
return RCTJSONParseWithOptions(jsonString, error, NSJSONReadingAllowFragments);
}
id RCTJSONParseMutable(NSString *jsonString, NSError **error) {
return RCTJSONParseWithOptions(jsonString, error, NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves);
} }
id RCTJSONClean(id object) id RCTJSONClean(id object)

View File

@ -61,6 +61,34 @@ static id RCTReadFile(NSString *filePath, NSString *key, NSDictionary **errorOut
return nil; return nil;
} }
// Only merges objects - all other types are just clobbered (including arrays)
static void RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *source)
{
for (NSString *key in source) {
id sourceValue = source[key];
if ([sourceValue isKindOfClass:[NSDictionary class]]) {
id destinationValue = destination[key];
NSMutableDictionary *nestedDestination;
if ([destinationValue classForCoder] == [NSMutableDictionary class]) {
nestedDestination = destinationValue;
} else {
if ([destinationValue isKindOfClass:[NSDictionary class]]) {
// Ideally we wouldn't eagerly copy here...
nestedDestination = [destinationValue mutableCopy];
} else {
destination[key] = [sourceValue copy];
}
}
if (nestedDestination) {
RCTMergeRecursive(nestedDestination, sourceValue);
destination[key] = nestedDestination;
}
} else {
destination[key] = sourceValue;
}
}
}
#pragma mark - RCTAsyncLocalStorage #pragma mark - RCTAsyncLocalStorage
@implementation RCTAsyncLocalStorage @implementation RCTAsyncLocalStorage
@ -135,13 +163,19 @@ RCT_EXPORT_MODULE()
if (errorOut) { if (errorOut) {
return errorOut; return errorOut;
} }
id value = [self _getValueForKey:key errorOut:&errorOut];
[result addObject:@[key, value ?: [NSNull null]]]; // Insert null if missing or failure.
return errorOut;
}
- (NSString *)_getValueForKey:(NSString *)key errorOut:(NSDictionary **)errorOut
{
id value = _manifest[key]; // nil means missing, null means there is a data file, anything else is an inline value. id value = _manifest[key]; // nil means missing, null means there is a data file, anything else is an inline value.
if (value == [NSNull null]) { if (value == [NSNull null]) {
NSString *filePath = [self _filePathForKey:key]; NSString *filePath = [self _filePathForKey:key];
value = RCTReadFile(filePath, key, &errorOut); value = RCTReadFile(filePath, key, errorOut);
} }
[result addObject:@[key, value ?: [NSNull null]]]; // Insert null if missing or failure. return value;
return errorOut;
} }
- (id)_writeEntry:(NSArray *)entry - (id)_writeEntry:(NSArray *)entry
@ -198,7 +232,6 @@ RCT_EXPORT_METHOD(multiGet:(NSArray *)keys
id keyError = [self _appendItemForKey:key toArray:result]; id keyError = [self _appendItemForKey:key toArray:result];
RCTAppendError(keyError, &errors); RCTAppendError(keyError, &errors);
} }
[self _writeManifest:&errors];
callback(@[errors ?: [NSNull null], result]); callback(@[errors ?: [NSNull null], result]);
} }
@ -221,6 +254,38 @@ RCT_EXPORT_METHOD(multiSet:(NSArray *)kvPairs
} }
} }
RCT_EXPORT_METHOD(multiMerge:(NSArray *)kvPairs
callback:(RCTResponseSenderBlock)callback)
{
id errorOut = [self _ensureSetup];
if (errorOut) {
callback(@[@[errorOut]]);
return;
}
NSMutableArray *errors;
for (__strong NSArray *entry in kvPairs) {
id keyError;
NSString *value = [self _getValueForKey:entry[0] errorOut:&keyError];
if (keyError) {
RCTAppendError(keyError, &errors);
} else {
if (value) {
NSMutableDictionary *mergedVal = [RCTJSONParseMutable(value, &keyError) mutableCopy];
RCTMergeRecursive(mergedVal, RCTJSONParse(entry[1], &keyError));
entry = @[entry[0], RCTJSONStringify(mergedVal, &keyError)];
}
if (!keyError) {
keyError = [self _writeEntry:entry];
}
RCTAppendError(keyError, &errors);
}
}
[self _writeManifest:&errors];
if (callback) {
callback(@[errors ?: [NSNull null]]);
}
}
RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys
callback:(RCTResponseSenderBlock)callback) callback:(RCTResponseSenderBlock)callback)
{ {

View File

@ -10,6 +10,14 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "RCTBridgeModule.h" #import "RCTBridgeModule.h"
#import "RCTConvert.h"
@interface RCTConvert (UIStatusBar)
+ (UIStatusBarStyle)UIStatusBarStyle:(id)json;
+ (UIStatusBarAnimation)UIStatusBarAnimation:(id)json;
@end
@interface RCTStatusBarManager : NSObject <RCTBridgeModule> @interface RCTStatusBarManager : NSObject <RCTBridgeModule>

View File

@ -11,6 +11,21 @@
#import "RCTLog.h" #import "RCTLog.h"
@implementation RCTConvert (UIStatusBar)
RCT_ENUM_CONVERTER(UIStatusBarStyle, (@{
@"default": @(UIStatusBarStyleDefault),
@"light-content": @(UIStatusBarStyleLightContent),
}), UIStatusBarStyleDefault, integerValue);
RCT_ENUM_CONVERTER(UIStatusBarAnimation, (@{
@"none": @(UIStatusBarAnimationNone),
@"fade": @(UIStatusBarAnimationFade),
@"slide": @(UIStatusBarAnimationSlide),
}), UIStatusBarAnimationNone, integerValue);
@end
@implementation RCTStatusBarManager @implementation RCTStatusBarManager
static BOOL RCTViewControllerBasedStatusBarAppearance() static BOOL RCTViewControllerBasedStatusBarAppearance()
@ -18,7 +33,8 @@ static BOOL RCTViewControllerBasedStatusBarAppearance()
static BOOL value; static BOOL value;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
value = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"] ?: @YES boolValue]; value = [[[NSBundle mainBundle] objectForInfoDictionaryKey:
@"UIViewControllerBasedStatusBarAppearance"] ?: @YES boolValue];
}); });
return value; return value;
@ -55,19 +71,4 @@ RCT_EXPORT_METHOD(setHidden:(BOOL)hidden
} }
} }
- (NSDictionary *)constantsToExport
{
return @{
@"Style": @{
@"default": @(UIStatusBarStyleDefault),
@"lightContent": @(UIStatusBarStyleLightContent),
},
@"Animation": @{
@"none": @(UIStatusBarAnimationNone),
@"fade": @(UIStatusBarAnimationFade),
@"slide": @(UIStatusBarAnimationSlide),
},
};
}
@end @end

View File

@ -1397,11 +1397,6 @@ RCT_EXPORT_METHOD(clearJSResponder)
NSMutableDictionary *allJSConstants = [@{ NSMutableDictionary *allJSConstants = [@{
@"customBubblingEventTypes": [self customBubblingEventTypes], @"customBubblingEventTypes": [self customBubblingEventTypes],
@"customDirectEventTypes": [self customDirectEventTypes], @"customDirectEventTypes": [self customDirectEventTypes],
@"NSTextAlignment": @{
@"Left": @(NSTextAlignmentLeft),
@"Center": @(NSTextAlignmentCenter),
@"Right": @(NSTextAlignmentRight),
},
@"Dimensions": @{ @"Dimensions": @{
@"window": @{ @"window": @{
@"width": @(RCTScreenSize().width), @"width": @(RCTScreenSize().width),
@ -1413,73 +1408,6 @@ RCT_EXPORT_METHOD(clearJSResponder)
@"height": @(RCTScreenSize().height), @"height": @(RCTScreenSize().height),
}, },
}, },
@"StyleConstants": @{
@"PointerEventsValues": @{
@"none": @(RCTPointerEventsNone),
@"box-none": @(RCTPointerEventsBoxNone),
@"box-only": @(RCTPointerEventsBoxOnly),
@"auto": @(RCTPointerEventsUnspecified),
},
},
@"UIText": @{
@"AutocapitalizationType": @{
@"characters": @(UITextAutocapitalizationTypeAllCharacters),
@"sentences": @(UITextAutocapitalizationTypeSentences),
@"words": @(UITextAutocapitalizationTypeWords),
@"none": @(UITextAutocapitalizationTypeNone),
},
},
@"UITextField": @{
@"clearButtonMode": @{
@"never": @(UITextFieldViewModeNever),
@"while-editing": @(UITextFieldViewModeWhileEditing),
@"unless-editing": @(UITextFieldViewModeUnlessEditing),
@"always": @(UITextFieldViewModeAlways),
},
},
@"UIKeyboardType": @{
@"default": @(UIKeyboardTypeDefault),
@"ascii-capable": @(UIKeyboardTypeASCIICapable),
@"numbers-and-punctuation": @(UIKeyboardTypeNumbersAndPunctuation),
@"url": @(UIKeyboardTypeURL),
@"number-pad": @(UIKeyboardTypeNumberPad),
@"phone-pad": @(UIKeyboardTypePhonePad),
@"name-phone-pad": @(UIKeyboardTypeNamePhonePad),
@"decimal-pad": @(UIKeyboardTypeDecimalPad),
@"email-address": @(UIKeyboardTypeEmailAddress),
@"twitter": @(UIKeyboardTypeTwitter),
@"web-search": @(UIKeyboardTypeWebSearch),
},
@"UIReturnKeyType": @{
@"default": @(UIReturnKeyDefault),
@"go": @(UIReturnKeyGo),
@"google": @(UIReturnKeyGoogle),
@"join": @(UIReturnKeyJoin),
@"next": @(UIReturnKeyNext),
@"route": @(UIReturnKeyRoute),
@"search": @(UIReturnKeySearch),
@"send": @(UIReturnKeySend),
@"yahoo": @(UIReturnKeyYahoo),
@"done": @(UIReturnKeyDone),
@"emergency-call": @(UIReturnKeyEmergencyCall),
},
@"UIView": @{
@"ContentMode": @{
@"ScaleToFill": @(UIViewContentModeScaleToFill),
@"ScaleAspectFit": @(UIViewContentModeScaleAspectFit),
@"ScaleAspectFill": @(UIViewContentModeScaleAspectFill),
@"Redraw": @(UIViewContentModeRedraw),
@"Center": @(UIViewContentModeCenter),
@"Top": @(UIViewContentModeTop),
@"Bottom": @(UIViewContentModeBottom),
@"Left": @(UIViewContentModeLeft),
@"Right": @(UIViewContentModeRight),
@"TopLeft": @(UIViewContentModeTopLeft),
@"TopRight": @(UIViewContentModeTopRight),
@"BottomLeft": @(UIViewContentModeBottomLeft),
@"BottomRight": @(UIViewContentModeBottomRight),
},
},
} mutableCopy]; } mutableCopy];
[_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, RCTViewManager *manager, BOOL *stop) { [_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, RCTViewManager *manager, BOOL *stop) {

View File

@ -16,6 +16,7 @@
134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */; }; 134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */; };
134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */; }; 134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */; };
134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3C1A6E7F0800051CC8 /* RCTWebViewExecutor.m */; }; 134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3C1A6E7F0800051CC8 /* RCTWebViewExecutor.m */; };
13513F3C1B1F43F400FCE529 /* RCTProgressViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */; };
13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */; }; 13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */; };
1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7091AB030C200659ED6 /* RCTAppState.m */; }; 1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7091AB030C200659ED6 /* RCTAppState.m */; };
137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E01AA5CF210034F82E /* RCTTabBar.m */; }; 137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E01AA5CF210034F82E /* RCTTabBar.m */; };
@ -104,6 +105,8 @@
134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTContextExecutor.m; sourceTree = "<group>"; }; 134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTContextExecutor.m; sourceTree = "<group>"; };
134FCB3B1A6E7F0800051CC8 /* RCTWebViewExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewExecutor.h; sourceTree = "<group>"; }; 134FCB3B1A6E7F0800051CC8 /* RCTWebViewExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewExecutor.h; sourceTree = "<group>"; };
134FCB3C1A6E7F0800051CC8 /* RCTWebViewExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebViewExecutor.m; sourceTree = "<group>"; }; 134FCB3C1A6E7F0800051CC8 /* RCTWebViewExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebViewExecutor.m; sourceTree = "<group>"; };
13513F3A1B1F43F400FCE529 /* RCTProgressViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTProgressViewManager.h; sourceTree = "<group>"; };
13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTProgressViewManager.m; sourceTree = "<group>"; };
13723B4E1A82FD3C00F88898 /* RCTStatusBarManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStatusBarManager.h; sourceTree = "<group>"; }; 13723B4E1A82FD3C00F88898 /* RCTStatusBarManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStatusBarManager.h; sourceTree = "<group>"; };
13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStatusBarManager.m; sourceTree = "<group>"; }; 13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStatusBarManager.m; sourceTree = "<group>"; };
1372B7081AB030C200659ED6 /* RCTAppState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAppState.h; sourceTree = "<group>"; }; 1372B7081AB030C200659ED6 /* RCTAppState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAppState.h; sourceTree = "<group>"; };
@ -307,6 +310,8 @@
58114A141AAE854800E7D092 /* RCTPickerManager.h */, 58114A141AAE854800E7D092 /* RCTPickerManager.h */,
58114A151AAE854800E7D092 /* RCTPickerManager.m */, 58114A151AAE854800E7D092 /* RCTPickerManager.m */,
13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */, 13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */,
13513F3A1B1F43F400FCE529 /* RCTProgressViewManager.h */,
13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */,
131B6AF01AF1093D00FFC3E0 /* RCTSegmentedControl.h */, 131B6AF01AF1093D00FFC3E0 /* RCTSegmentedControl.h */,
131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */, 131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */,
131B6AF21AF1093D00FFC3E0 /* RCTSegmentedControlManager.h */, 131B6AF21AF1093D00FFC3E0 /* RCTSegmentedControlManager.h */,
@ -524,6 +529,7 @@
58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */, 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */,
832348161A77A5AA00B55238 /* Layout.c in Sources */, 832348161A77A5AA00B55238 /* Layout.c in Sources */,
14F4D38B1AE1B7E40049C042 /* RCTProfile.m in Sources */, 14F4D38B1AE1B7E40049C042 /* RCTProfile.m in Sources */,
13513F3C1B1F43F400FCE529 /* RCTProgressViewManager.m in Sources */,
14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */, 14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */,
14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */, 14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */,
13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */, 13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */,

View File

@ -8,6 +8,13 @@
*/ */
#import "RCTViewManager.h" #import "RCTViewManager.h"
#import "RCTConvert.h"
@interface RCTConvert(UIDatePicker)
+ (UIDatePickerMode)UIDatePickerMode:(id)json;
@end
@interface RCTDatePickerManager : RCTViewManager @interface RCTDatePickerManager : RCTViewManager

View File

@ -10,7 +10,6 @@
#import "RCTDatePickerManager.h" #import "RCTDatePickerManager.h"
#import "RCTBridge.h" #import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h" #import "RCTEventDispatcher.h"
#import "UIView+React.h" #import "UIView+React.h"
@ -20,7 +19,7 @@ RCT_ENUM_CONVERTER(UIDatePickerMode, (@{
@"time": @(UIDatePickerModeTime), @"time": @(UIDatePickerModeTime),
@"date": @(UIDatePickerModeDate), @"date": @(UIDatePickerModeDate),
@"datetime": @(UIDatePickerModeDateAndTime), @"datetime": @(UIDatePickerModeDateAndTime),
//@"countdown": @(UIDatePickerModeCountDownTimer) // not supported yet @"countdown": @(UIDatePickerModeCountDownTimer), // not supported yet
}), UIDatePickerModeTime, integerValue) }), UIDatePickerModeTime, integerValue)
@end @end
@ -31,9 +30,12 @@ RCT_EXPORT_MODULE()
- (UIView *)view - (UIView *)view
{ {
// TODO: we crash here if the RCTDatePickerManager is released
// while the UIDatePicker is still sending onChange events. To
// fix this we should maybe subclass UIDatePicker and make it
// be its own event target.
UIDatePicker *picker = [[UIDatePicker alloc] init]; UIDatePicker *picker = [[UIDatePicker alloc] init];
[picker addTarget:self [picker addTarget:self action:@selector(onChange:)
action:@selector(onChange:)
forControlEvents:UIControlEventValueChanged]; forControlEvents:UIControlEventValueChanged];
return picker; return picker;
} }
@ -56,17 +58,10 @@ RCT_REMAP_VIEW_PROPERTY(timeZoneOffsetInMinutes, timeZone, NSTimeZone)
- (NSDictionary *)constantsToExport - (NSDictionary *)constantsToExport
{ {
UIDatePicker *dp = [[UIDatePicker alloc] init]; UIDatePicker *view = [[UIDatePicker alloc] init];
[dp layoutIfNeeded];
return @{ return @{
@"ComponentHeight": @(CGRectGetHeight(dp.frame)), @"ComponentHeight": @(view.intrinsicContentSize.height),
@"ComponentWidth": @(CGRectGetWidth(dp.frame)), @"ComponentWidth": @(view.intrinsicContentSize.width),
@"DatePickerModes": @{
@"time": @(UIDatePickerModeTime),
@"date": @(UIDatePickerModeDate),
@"datetime": @(UIDatePickerModeDateAndTime),
}
}; };
} }

View File

@ -27,10 +27,10 @@ RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger)
- (NSDictionary *)constantsToExport - (NSDictionary *)constantsToExport
{ {
RCTPicker *pv = [[RCTPicker alloc] init]; RCTPicker *view = [[RCTPicker alloc] init];
return @{ return @{
@"ComponentHeight": @(CGRectGetHeight(pv.frame)), @"ComponentHeight": @(view.intrinsicContentSize.height),
@"ComponentWidth": @(CGRectGetWidth(pv.frame)) @"ComponentWidth": @(view.intrinsicContentSize.width)
}; };
} }

View File

@ -0,0 +1,14 @@
/**
* 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 "RCTViewManager.h"
@interface RCTProgressViewManager : RCTViewManager
@end

View File

@ -0,0 +1,47 @@
/**
* 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 "RCTProgressViewManager.h"
#import "RCTConvert.h"
@implementation RCTConvert (RCTProgressViewManager)
RCT_ENUM_CONVERTER(UIProgressViewStyle, (@{
@"default": @(UIProgressViewStyleDefault),
@"bar": @(UIProgressViewStyleBar),
}), UIProgressViewStyleDefault, integerValue)
@end
@implementation RCTProgressViewManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[UIProgressView alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(progressViewStyle, UIProgressViewStyle)
RCT_EXPORT_VIEW_PROPERTY(progress, float)
RCT_EXPORT_VIEW_PROPERTY(progressTintColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(trackTintColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(progressImage, UIImage)
RCT_EXPORT_VIEW_PROPERTY(trackImage, UIImage)
- (NSDictionary *)constantsToExport
{
UIProgressView *view = [[UIProgressView alloc] init];
return @{
@"ComponentHeight": @(view.intrinsicContentSize.height),
};
}
@end

View File

@ -8,6 +8,13 @@
*/ */
#import "RCTViewManager.h" #import "RCTViewManager.h"
#import "RCTConvert.h"
@interface RCTConvert (UIScrollView)
+ (UIScrollViewKeyboardDismissMode)UIScrollViewKeyboardDismissMode:(id)json;
@end
@interface RCTScrollViewManager : RCTViewManager @interface RCTScrollViewManager : RCTViewManager

View File

@ -10,11 +10,22 @@
#import "RCTScrollViewManager.h" #import "RCTScrollViewManager.h"
#import "RCTBridge.h" #import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTScrollView.h" #import "RCTScrollView.h"
#import "RCTSparseArray.h" #import "RCTSparseArray.h"
#import "RCTUIManager.h" #import "RCTUIManager.h"
@implementation RCTConvert (UIScrollView)
RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{
@"none": @(UIScrollViewKeyboardDismissModeNone),
@"on-drag": @(UIScrollViewKeyboardDismissModeOnDrag),
@"interactive": @(UIScrollViewKeyboardDismissModeInteractive),
// Backwards compatibility
@"onDrag": @(UIScrollViewKeyboardDismissModeOnDrag),
}), UIScrollViewKeyboardDismissModeNone, integerValue)
@end
@implementation RCTScrollViewManager @implementation RCTScrollViewManager
RCT_EXPORT_MODULE() RCT_EXPORT_MODULE()
@ -53,14 +64,10 @@ RCT_DEPRECATED_VIEW_PROPERTY(throttleScrollCallbackMS, scrollEventThrottle)
- (NSDictionary *)constantsToExport - (NSDictionary *)constantsToExport
{ {
return @{ return @{
// TODO: unused - remove these?
@"DecelerationRate": @{ @"DecelerationRate": @{
@"Normal": @(UIScrollViewDecelerationRateNormal), @"normal": @(UIScrollViewDecelerationRateNormal),
@"Fast": @(UIScrollViewDecelerationRateFast), @"fast": @(UIScrollViewDecelerationRateFast),
},
@"KeyboardDismissMode": @{
@"None": @(UIScrollViewKeyboardDismissModeNone),
@"Interactive": @(UIScrollViewKeyboardDismissModeInteractive),
@"OnDrag": @(UIScrollViewKeyboardDismissModeOnDrag),
}, },
}; };
} }

View File

@ -25,7 +25,7 @@ RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(caretHidden, BOOL) RCT_EXPORT_VIEW_PROPERTY(caretHidden, BOOL)
RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL) RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL)
RCT_EXPORT_VIEW_PROPERTY(enabled, BOOL) RCT_REMAP_VIEW_PROPERTY(editable, enabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString) RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)
RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor) RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(text, NSString) RCT_EXPORT_VIEW_PROPERTY(text, NSString)
@ -36,6 +36,7 @@ RCT_EXPORT_VIEW_PROPERTY(keyboardType, UIKeyboardType)
RCT_EXPORT_VIEW_PROPERTY(returnKeyType, UIReturnKeyType) RCT_EXPORT_VIEW_PROPERTY(returnKeyType, UIReturnKeyType)
RCT_EXPORT_VIEW_PROPERTY(enablesReturnKeyAutomatically, BOOL) RCT_EXPORT_VIEW_PROPERTY(enablesReturnKeyAutomatically, BOOL)
RCT_EXPORT_VIEW_PROPERTY(secureTextEntry, BOOL) RCT_EXPORT_VIEW_PROPERTY(secureTextEntry, BOOL)
RCT_REMAP_VIEW_PROPERTY(password, secureTextEntry, BOOL) // backwards compatibility
RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor) RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor)
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType, UITextAutocapitalizationType) RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType, UITextAutocapitalizationType)
RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextField) RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextField)

View File

@ -62,7 +62,7 @@ if the option is boolean `1/0` or `true/false` is accepted.
Here are the current options the packager accepts: Here are the current options the packager accepts:
* `dev` boolean, defaults to true: sets a global `__DEV__` variable * `dev` boolean, defaults to true: sets a global `__DEV__` variable
which will effect how the React Nativeg core libraries behave. which will effect how the React Native core libraries behave.
* `minify` boolean, defaults to false: whether to minify the bundle. * `minify` boolean, defaults to false: whether to minify the bundle.
* `runModule` boolean, defaults to true: whether to require your entry * `runModule` boolean, defaults to true: whether to require your entry
point module. So if you requested `moduleName`, this option will add point module. So if you requested `moduleName`, this option will add