Updates from Mon 30 Mar

- [ReactNative] Clean up no longer needed reference to NavigationBarClass | Philipp von Weitershausen
- [TextInput] returnKeyType, enablesReturnKeyAutomatically, secureTextEntry, more keyboardTypes | Tadeu Zagallo
- [ReactNative] PropTypes for NavigationBars | Philipp von Weitershausen
- Changed LayoutAnimation to use ms instead of seconds for consistency | Nick Lockwood
- Better date support | Nick Lockwood
- Renamed throttleScrollCallbackMS to scrollEventThrottle | Nick Lockwood
- Fixed threading issues in RCTImageDownloader | Nick Lockwood
- [iOS][Assets]: Cleaning up more 1x png from Libraries | Radu Marin
- [ReactNative][docs] LinkingIOS | Tadeu Zagallo
- Fixing TouchableOpacity and TouchableHighlight documentation | Ben Alpert
- [react-native] Add React.addons.createFragment | Ben Alpert
This commit is contained in:
Christopher Chedeau 2015-03-30 20:12:32 -07:00
parent 02298b59e2
commit 9a4ee17adb
49 changed files with 904 additions and 288 deletions

View File

@ -31,8 +31,16 @@ var MovieScreen = require('./MovieScreen');
var fetch = require('fetch');
/**
* This is for demo purposes only, and rate limited.
* In case you want to use the Rotten Tomatoes' API on a real app you should
* create an account at http://developer.rottentomatoes.com/
*/
var API_URL = 'http://api.rottentomatoes.com/api/public/v1.0/';
var API_KEYS = ['7waqfqbprs7pajbz28mqf6vz', 'y4vwv8m33hed9ety83jmv52f'];
var API_KEYS = [
'7waqfqbprs7pajbz28mqf6vz',
// 'y4vwv8m33hed9ety83jmv52f', Fallback api_key
];
// Results should be cached keyed by the query
// with values of null meaning "being fetched"

View File

@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
00481BE81AC0C86700671115 /* libRCTWebSocketDebugger.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00481BE61AC0C7FA00671115 /* libRCTWebSocketDebugger.a */; };
00481BEA1AC0C89D00671115 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 00481BE91AC0C89D00671115 /* libicucore.dylib */; };
008F07F31AC5B25A0029DE68 /* main.jsbundle in Resources */ = {isa = PBXBuildFile; fileRef = 008F07F21AC5B25A0029DE68 /* main.jsbundle */; };
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
00C302E61ABCBA2D00DB3ED1 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302B41ABCB8E700DB3ED1 /* libRCTAdSupport.a */; };
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
@ -92,6 +93,7 @@
/* Begin PBXFileReference section */
00481BDB1AC0C7FA00671115 /* RCTWebSocketDebugger.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocketDebugger.xcodeproj; path = ../../Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj; sourceTree = "<group>"; };
00481BE91AC0C89D00671115 /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; };
008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = main.jsbundle; path = iOS/main.jsbundle; sourceTree = "<group>"; };
00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = "<group>"; };
00C302AF1ABCB8E700DB3ED1 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = "<group>"; };
00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = "<group>"; };
@ -189,6 +191,7 @@
13B07FAE1A68108700A75B9A /* SampleApp */ = {
isa = PBXGroup;
children = (
008F07F21AC5B25A0029DE68 /* main.jsbundle */,
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
13B07FB01A68108700A75B9A /* AppDelegate.m */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
@ -405,6 +408,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
008F07F31AC5B25A0029DE68 /* main.jsbundle in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
);

View File

@ -31,7 +31,7 @@
// OPTION 2
// Load from pre-bundled file on disk. To re-generate the static bundle, run
//
// $ curl http://localhost:8081/Examples/SampleApp/index.ios.bundle -o main.jsbundle
// $ curl 'http://localhost:8081/Examples/SampleApp/index.ios.bundle?dev=false&minify=true' -o iOS/main.jsbundle
//
// and uncomment the next following line
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];

View File

@ -0,0 +1,5 @@
// Offline JS
// To re-generate the offline bundle, run this from root of your project
// $ curl 'http://localhost:8081/Examples/SampleApp/index.ios.bundle?dev=false&minify=true' -o iOS/main.jsbundle
throw new Error('Offline JS file is empty. See iOS/main.jsbundle for instructions');

View File

@ -226,9 +226,9 @@ var styles = StyleSheet.create({
var animations = {
layout: {
spring: {
duration: 0.75,
duration: 750,
create: {
duration: 0.3,
duration: 300,
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity,
},
@ -238,13 +238,13 @@ var animations = {
},
},
easeInEaseOut: {
duration: 0.3,
duration: 300,
create: {
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.scaleXY,
},
update: {
delay: 0.1,
delay: 100,
type: LayoutAnimation.Types.easeInEaseOut,
},
},

View File

@ -33,7 +33,7 @@ exports.examples = [
return (
<ScrollView
onScroll={() => { console.log('onScroll!'); }}
throttleScrollCallbackMS={200}
scrollEventThrottle={200}
contentInset={{top: -50}}
style={styles.scrollView}>
{THUMBS.map(createThumbRow)}

View File

@ -76,6 +76,15 @@ exports.examples = [
</Text>
);
},
}, {
title: 'Padding',
render: function() {
return (
<Text style={{padding: 10}}>
This text is indented by 10px padding on all sides.
</Text>
);
},
}, {
title: 'Font Family',
render: function() {

View File

@ -174,6 +174,89 @@ exports.examples = [
);
}
},
{
title: 'Keyboard types',
render: function() {
var keyboardTypes = [
'default',
'ascii-capable',
'numbers-and-punctuation',
'url',
'number-pad',
'phone-pad',
'name-phone-pad',
'email-address',
'decimal-pad',
'twitter',
'web-search',
'numeric',
];
var examples = keyboardTypes.map((type) => {
return (
<WithLabel key={type} label={type}>
<TextInput
keyboardType={type}
style={styles.default}
/>
</WithLabel>
);
});
return <View>{examples}</View>;
}
},
{
title: 'Return key types',
render: function() {
var returnKeyTypes = [
'default',
'go',
'google',
'join',
'next',
'route',
'search',
'send',
'yahoo',
'done',
'emergency-call',
];
var examples = returnKeyTypes.map((type) => {
return (
<WithLabel key={type} label={type}>
<TextInput
returnKeyType={type}
style={styles.default}
/>
</WithLabel>
);
});
return <View>{examples}</View>;
}
},
{
title: 'Enable return key automatically',
render: function() {
return (
<View>
<WithLabel label="true">
<TextInput enablesReturnKeyAutomatically={true} style={styles.default} />
</WithLabel>
</View>
);
}
},
{
title: 'Secure text entry',
render: function() {
return (
<View>
<WithLabel label="true">
<TextInput secureTextEntry={true} style={styles.default} value="abc" />
</WithLabel>
</View>
);
}
},
{
title: 'Event handling',
render: function(): ReactElement { return <TextEventsExample /> },

View File

@ -0,0 +1,72 @@
diff a/Libraries/FBReactKit/js/react-native-github/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Libraries/FBReactKit/js/react-native-github/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj (rejected hunks)
@@ -19,6 +19,7 @@
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */; };
+ D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -78,6 +79,13 @@
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RCTActionSheet;
};
+ D85B829B1AB6D5CE003F4FE2 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 832C81801AAF6DEF007FA2F7;
+ remoteInfo = RCTVibration;
+ };
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
@@ -98,6 +106,7 @@
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = "<group>"; };
+ D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../../Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -112,6 +121,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */,
147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */,
134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */,
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */,
@@ -145,6 +155,7 @@
1316A21D1AA397F400C0188E /* Libraries */ = {
isa = PBXGroup;
children = (
+ D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */,
14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */,
13417FFA1AA91531003F314A /* ReactKit.xcodeproj */,
134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */,
@@ -334,6 +353,10 @@
ProjectRef = 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */;
},
{
+ ProductGroup = D85B82921AB6D5CE003F4FE2 /* Products */;
+ ProjectRef = D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */;
+ },
+ {
ProductGroup = 13417FFB1AA91531003F314A /* Products */;
ProjectRef = 13417FFA1AA91531003F314A /* ReactKit.xcodeproj */;
},
@@ -396,6 +419,13 @@
remoteRef = 147CED4A1AB34F8C00DA3E4C /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
+ D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = libRCTVibration.a;
+ remoteRef = D85B829B1AB6D5CE003F4FE2 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:UIExplorer.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,10 +1,5 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "story-background.png"
},
{
"idiom" : "universal",
"scale" : "2x",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 KiB

After

Width:  |  Height:  |  Size: 260 KiB

View File

@ -211,21 +211,27 @@ var ticksPerSecond = 60;
var lastUsedTag = 0;
module.exports = {
/**
* Returns a new unique animation tag.
*/
allocateTag: function(): number {
return ++lastUsedTag;
},
/**
* Returns the values of the easing function at discrete ticks (60 per second).
*/
evaluateEasingFunction: function(duration: number, easing: string | EasingFunction): Array<number> {
if (typeof easing === 'string') {
easing = defaults[easing] || defaults.easeOutQuad;
}
var tickCount = Math.round(duration * ticksPerSecond / 1000);
var sample = [];
var samples = [];
for (var i = 0; i <= tickCount; i++) {
sample.push(easing(i / tickCount));
samples.push(easing.call(defaults, i / tickCount));
}
return sample;
return samples;
},
};

View File

@ -92,13 +92,13 @@ var LayoutAnimation = {
configChecker: configChecker,
Presets: {
easeInEaseOut: create(
0.3, Types.easeInEaseOut, Properties.opacity
300, Types.easeInEaseOut, Properties.opacity
),
linear: create(
0.5, Types.linear, Properties.opacity
500, Types.linear, Properties.opacity
),
spring: {
duration: 0.7,
duration: 700,
create: {
type: Types.linear,
property: Properties.opacity,

View File

@ -11,7 +11,6 @@
*/
'use strict';
var NativeModules = require('NativeModules');
var NativeModules = require('NativeModules');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var Subscribable = require('Subscribable');

View File

@ -64,7 +64,7 @@ var ScrollView = React.createClass({
showsHorizontalScrollIndicator: PropTypes.bool,
showsVerticalScrollIndicator: PropTypes.bool,
style: StyleSheetPropType(ViewStylePropTypes),
throttleScrollCallbackMS: PropTypes.number, // null
scrollEventThrottle: PropTypes.number, // null
/**
* When true, the scroll view bounces horizontally when it reaches the end
@ -211,12 +211,12 @@ var ScrollView = React.createClass({
);
}
if (__DEV__) {
if (this.props.onScroll && !this.props.throttleScrollCallbackMS) {
if (this.props.onScroll && !this.props.scrollEventThrottle) {
var onScroll = this.props.onScroll;
this.props.onScroll = function() {
console.log(
'You specified `onScroll` on a <ScrollView> but not ' +
'`throttleScrollCallbackMS`. You will only receive one event. ' +
'`scrollEventThrottle`. You will only receive one event. ' +
'Using `16` you get all the events but be aware that it may ' +
'cause frame drops, use a bigger number if you don\'t need as ' +
'much precision.'
@ -325,7 +325,7 @@ var validAttributes = {
showsHorizontalScrollIndicator: true,
showsVerticalScrollIndicator: true,
stickyHeaderIndices: {diff: deepDiffer},
throttleScrollCallbackMS: true,
scrollEventThrottle: true,
zoomScale: true,
};

View File

@ -15,6 +15,7 @@ var DocumentSelectionState = require('DocumentSelectionState');
var EventEmitter = require('EventEmitter');
var NativeMethodsMixin = require('NativeMethodsMixin');
var RCTUIManager = require('NativeModules').UIManager;
var Platform = require('Platform');
var PropTypes = require('ReactPropTypes');
var React = require('React');
var ReactChildren = require('ReactChildren');
@ -27,12 +28,12 @@ var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
var emptyFunction = require('emptyFunction');
var getObjectValues = require('getObjectValues');
var invariant = require('invariant');
var merge = require('merge');
var autoCapitalizeConsts = RCTUIManager.UIText.AutocapitalizationType;
var clearButtonModeConsts = RCTUIManager.UITextField.clearButtonMode;
var keyboardTypeConsts = RCTUIManager.UIKeyboardType;
var returnKeyTypeConsts = RCTUIManager.UIReturnKeyType;
var RCTTextViewAttributes = merge(ReactIOSViewAttributes.UIView, {
autoCorrect: true,
@ -44,6 +45,9 @@ var RCTTextViewAttributes = merge(ReactIOSViewAttributes.UIView, {
fontStyle: true,
fontWeight: true,
keyboardType: true,
returnKeyType: true,
enablesReturnKeyAutomatically: true,
secureTextEntry: true,
mostRecentEventCounter: true,
placeholder: true,
placeholderTextColor: true,
@ -66,6 +70,27 @@ var notMultiline = {
onSubmitEditing: true,
};
var TextInputAndroidAttributes = {
autoCapitalize: true,
autoCorrect: true,
autoFocus: true,
keyboardType: true,
multiline: true,
password: true,
placeholder: true,
value: true,
testID: true,
};
var AndroidTextInput = createReactIOSNativeComponentClass({
validAttributes: TextInputAndroidAttributes,
uiViewClassName: 'AndroidTextInput',
});
var crossPlatformKeyboardTypeMap = {
'numeric': 'decimal-pad',
};
type DefaultProps = {
bufferDelay: number;
};
@ -138,8 +163,47 @@ var TextInput = React.createClass({
*/
keyboardType: PropTypes.oneOf([
'default',
// iOS
'ascii-capable',
'numbers-and-punctuation',
'url',
'number-pad',
'phone-pad',
'name-phone-pad',
'email-address',
'decimal-pad',
'twitter',
'web-search',
// Cross-platform
'numeric',
]),
/**
* Determines how the return key should look.
*/
returnKeyType: PropTypes.oneOf([
'default',
'go',
'google',
'join',
'next',
'route',
'search',
'send',
'yahoo',
'done',
'emergency-call',
]),
/**
* If true, the keyboard disables the return key when there is no text and
* automatically enables it when there is text. Default value is false.
*/
enablesReturnKeyAutomatically: PropTypes.bool,
/**
* If true, the text input obscures the text entered so that sensitive text
* like passwords stay secure. Default value is false.
*/
secureTextEntry: PropTypes.bool,
/**
* If true, the text input can be multiple lines. Default value is false.
*/
@ -157,10 +221,15 @@ var TextInput = React.createClass({
*
* Callback that is called when the text input's text changes.
*/
onChange: PropTypes.func,
onChangeText: PropTypes.func,
onEndEditing: PropTypes.func,
onSubmitEditing: PropTypes.func,
/**
* If true, the TextInput will be a password field. Default value is false.
*/
password: PropTypes.bool,
/**
* The string that will be rendered before text input has been entered
*/
@ -202,6 +271,10 @@ var TextInput = React.createClass({
]),
style: Text.propTypes.style,
/**
* Used to locate this view in end-to-end tests.
*/
testID: PropTypes.string,
},
/**
@ -313,10 +386,24 @@ var TextInput = React.createClass({
},
render: function() {
if (Platform.OS === 'ios') {
return this._renderIOs();
} else if (Platform.OS === 'android') {
return this._renderAndroid();
}
},
_renderIOs: function() {
var textContainer;
var autoCapitalize = autoCapitalizeConsts[this.props.autoCapitalize];
var clearButtonMode = clearButtonModeConsts[this.props.clearButtonMode];
var clearButtonMode = RCTUIManager.UITextField.clearButtonMode[this.props.clearButtonMode];
var keyboardType = keyboardTypeConsts[
crossPlatformKeyboardTypeMap[this.props.keyboardType] ||
this.props.keyboardType
];
var returnKeyType = returnKeyTypeConsts[this.props.returnKeyType];
if (!this.props.multiline) {
for (var propKey in onlyMultiline) {
@ -331,7 +418,10 @@ var TextInput = React.createClass({
ref="input"
style={[styles.input, this.props.style]}
enabled={this.props.editable}
keyboardType={this.props.keyboardType}
keyboardType={keyboardType}
returnKeyType={returnKeyType}
enablesReturnKeyAutomatically={this.props.enablesReturnKeyAutomatically}
secureTextEntry={this.props.secureTextEntry}
onFocus={this._onFocus}
onBlur={this._onBlur}
onChange={this._onChange}
@ -373,6 +463,10 @@ var TextInput = React.createClass({
children={children}
mostRecentEventCounter={this.state.mostRecentEventCounter}
editable={this.props.editable}
keyboardType={keyboardType}
returnKeyType={returnKeyType}
enablesReturnKeyAutomatically={this.props.enablesReturnKeyAutomatically}
secureTextEntry={this.props.secureTextEntry}
onFocus={this._onFocus}
onBlur={this._onBlur}
onChange={this._onChange}
@ -398,6 +492,27 @@ var TextInput = React.createClass({
);
},
_renderAndroid: function() {
var autoCapitalize = autoCapitalizeConsts[this.props.autoCapitalize];
return (
<AndroidTextInput
ref="input"
style={[this.props.style]}
autoCapitalize={autoCapitalize}
autoCorrect={this.props.autoCorrect}
keyboardType={this.props.keyboardType}
multiline={this.props.multiline}
onFocus={this._onFocus}
onBlur={this._onBlur}
onChange={this._onChange}
password={this.props.password}
placeholder={this.props.placeholder}
value={this.props.value}
testID={this.props.testID}
/>
);
},
_onFocus: function(event: Event) {
if (this.props.onFocus) {
this.props.onFocus(event);

View File

@ -50,7 +50,7 @@ var DEFAULT_PROPS = {
* style={styles.button}
* source={require('image!myButton')}
* />
* </View>
* </TouchableHighlight>
* );
* },
* ```

View File

@ -39,7 +39,7 @@ var onlyChild = require('onlyChild');
* style={styles.button}
* source={require('image!myButton')}
* />
* </View>
* </TouchableOpacity>
* );
* },
* ```

View File

@ -325,8 +325,8 @@ var ListView = React.createClass({
stickyHeaderIndices: sectionHeaderIndices,
}
);
if (!props.throttleScrollCallbackMS) {
props.throttleScrollCallbackMS = DEFAULT_SCROLL_CALLBACK_THROTTLE;
if (!props.scrollEventThrottle) {
props.scrollEventThrottle = DEFAULT_SCROLL_CALLBACK_THROTTLE;
}
return (

View File

@ -985,7 +985,6 @@ var Navigator = React.createClass({
},
renderNavigationStackBar: function() {
var NavigationBarClass = this.props.NavigationBarClass;
if (!this.props.navigationBar) {
return null;
}

View File

@ -46,15 +46,12 @@ var TITLE_PROPS = Interpolators.map(() => {return {style: {}};});
var RIGHT_BUTTON_PROPS = Interpolators.map(() => {return {style: {}};});
/**
* TODO: Rename `observedTopOfStack` to `presentedIndex` in `NavigationStack`.
*/
var navStatePresentedIndex = function(navState) {
if (navState.presentedIndex !== undefined) {
return navState.presentedIndex;
} else {
return navState.observedTopOfStack;
}
// TODO: rename `observedTopOfStack` to `presentedIndex` in `NavigatorIOS`
return navState.observedTopOfStack;
};
@ -85,7 +82,12 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
titleContentForRoute: PropTypes.func,
iconForRoute: PropTypes.func,
}),
navigationBarStyles: PropTypes.number,
navState: React.PropTypes.shape({
routeStack: React.PropTypes.arrayOf(React.PropTypes.object),
idStack: React.PropTypes.arrayOf(React.PropTypes.number),
presentedIndex: React.PropTypes.number,
}),
navigationBarStyles: View.propTypes.style,
},
statics: {

View File

@ -34,19 +34,31 @@ var View = require('View');
var COMPONENT_NAMES = ['Title', 'LeftButton', 'RightButton'];
/**
* TODO (janzer): Rename `observedTopOfStack` to `presentedIndex` in `NavigationStack`.
*/
var navStatePresentedIndex = function(navState) {
if (navState.presentedIndex !== undefined) {
return navState.presentedIndex;
} else {
return navState.observedTopOfStack;
}
// TODO: rename `observedTopOfStack` to `presentedIndex` in `NavigatorIOS`
return navState.observedTopOfStack;
};
var NavigatorNavigationBar = React.createClass({
propTypes: {
navigator: React.PropTypes.object,
navigationBarRouteMapper: React.PropTypes.shape({
Title: React.PropTypes.func.isRequired,
LeftButton: React.PropTypes.func.isRequired,
RightButton: React.PropTypes.func.isRequired,
}),
navState: React.PropTypes.shape({
routeStack: React.PropTypes.arrayOf(React.PropTypes.object),
idStack: React.PropTypes.arrayOf(React.PropTypes.number),
presentedIndex: React.PropTypes.number,
}),
navigationBarStyles: View.propTypes.style,
},
statics: {
Styles: NavigatorNavigationBarStyles,
},

View File

@ -16,14 +16,30 @@ typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error);
+ (instancetype)sharedInstance;
/**
* Downloads a block of raw data and returns it. Note that the callback block
* will not be executed on the same thread you called the method from, nor on
* the main thread. Returns a token that can be used to cancel the download.
*/
- (id)downloadDataForURL:(NSURL *)url
block:(RCTDataDownloadBlock)block;
/**
* Downloads an image and decompresses it a the size specified. The compressed
* image will be cached in memory and to disk. Note that the callback block
* will not be executed on the same thread you called the method from, nor on
* the main thread. Returns a token that can be used to cancel the download.
*/
- (id)downloadImageForURL:(NSURL *)url
size:(CGSize)size
scale:(CGFloat)scale
block:(RCTImageDownloadBlock)block;
/**
* Cancel an in-flight download. If multiple requets have been made for the
* same image, only the request that relates to the token passed will be
* cancelled.
*/
- (void)cancelDownload:(id)downloadToken;
@end

View File

@ -17,12 +17,12 @@ typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *e
@implementation RCTImageDownloader
{
RCTCache *_cache;
dispatch_queue_t _processingQueue;
NSMutableDictionary *_pendingBlocks;
}
+ (instancetype)sharedInstance
{
RCTAssertMainThread();
static RCTImageDownloader *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@ -35,27 +35,32 @@ typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *e
{
if ((self = [super init])) {
_cache = [[RCTCache alloc] initWithName:@"RCTImageDownloader"];
_pendingBlocks = [NSMutableDictionary dictionary];
_processingQueue = dispatch_queue_create("com.facebook.React.DownloadProcessingQueue", DISPATCH_QUEUE_SERIAL);
_pendingBlocks = [[NSMutableDictionary alloc] init];
}
return self;
}
- (NSString *)cacheKeyForURL:(NSURL *)url
static NSString *RCTCacheKeyForURL(NSURL *url)
{
return url.absoluteString;
}
- (id)_downloadDataForURL:(NSURL *)url block:(RCTCachedDataDownloadBlock)block
{
NSString *cacheKey = [self cacheKeyForURL:url];
NSString *cacheKey = RCTCacheKeyForURL(url);
__block BOOL cancelled = NO;
__block NSURLSessionDataTask *task = nil;
dispatch_block_t cancel = ^{
cancelled = YES;
NSMutableArray *pendingBlocks = _pendingBlocks[cacheKey];
[pendingBlocks removeObject:block];
dispatch_async(_processingQueue, ^{
NSMutableArray *pendingBlocks = self->_pendingBlocks[cacheKey];
[pendingBlocks removeObject:block];
});
if (task) {
[task cancel];
@ -63,56 +68,60 @@ typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *e
}
};
NSMutableArray *pendingBlocks = _pendingBlocks[cacheKey];
if (pendingBlocks) {
[pendingBlocks addObject:block];
} else {
_pendingBlocks[cacheKey] = [NSMutableArray arrayWithObject:block];
__weak RCTImageDownloader *weakSelf = self;
RCTCachedDataDownloadBlock runBlocks = ^(BOOL cached, NSData *data, NSError *error) {
RCTImageDownloader *strongSelf = weakSelf;
NSArray *blocks = strongSelf->_pendingBlocks[cacheKey];
[strongSelf->_pendingBlocks removeObjectForKey:cacheKey];
for (RCTCachedDataDownloadBlock block in blocks) {
block(cached, data, error);
}
};
if ([_cache hasDataForKey:cacheKey]) {
[_cache fetchDataForKey:cacheKey completionHandler:^(NSData *data) {
if (!cancelled) runBlocks(YES, data, nil);
}];
dispatch_async(_processingQueue, ^{
NSMutableArray *pendingBlocks = _pendingBlocks[cacheKey];
if (pendingBlocks) {
[pendingBlocks addObject:block];
} else {
task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!cancelled) runBlocks(NO, data, error);
}];
_pendingBlocks[cacheKey] = [NSMutableArray arrayWithObject:block];
[task resume];
__weak RCTImageDownloader *weakSelf = self;
RCTCachedDataDownloadBlock runBlocks = ^(BOOL cached, NSData *data, NSError *error) {
RCTImageDownloader *strongSelf = weakSelf;
NSArray *blocks = strongSelf->_pendingBlocks[cacheKey];
[strongSelf->_pendingBlocks removeObjectForKey:cacheKey];
for (RCTCachedDataDownloadBlock block in blocks) {
block(cached, data, error);
}
};
if ([_cache hasDataForKey:cacheKey]) {
[_cache fetchDataForKey:cacheKey completionHandler:^(NSData *data) {
if (!cancelled) {
runBlocks(YES, data, nil);
}
}];
} else {
task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!cancelled) {
runBlocks(NO, data, error);
}
}];
[task resume];
}
}
}
});
return [cancel copy];
}
- (id)downloadDataForURL:(NSURL *)url block:(RCTDataDownloadBlock)block
{
NSString *cacheKey = [self cacheKeyForURL:url];
NSString *cacheKey = RCTCacheKeyForURL(url);
__weak RCTImageDownloader *weakSelf = self;
return [self _downloadDataForURL:url block:^(BOOL cached, NSData *data, NSError *error) {
if (!cached) {
RCTImageDownloader *strongSelf = weakSelf;
[strongSelf->_cache setData:data forKey:cacheKey];
}
dispatch_async(dispatch_get_main_queue(), ^{
block(data, error);
});
block(data, error);
}];
}
- (id)downloadImageForURL:(NSURL *)url size:(CGSize)size scale:(CGFloat)scale block:(RCTImageDownloadBlock)block
- (id)downloadImageForURL:(NSURL *)url size:(CGSize)size
scale:(CGFloat)scale block:(RCTImageDownloadBlock)block
{
return [self downloadDataForURL:url block:^(NSData *data, NSError *error) {
@ -142,20 +151,14 @@ typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *e
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
// TODO: should we cache the decompressed image?
dispatch_async(dispatch_get_main_queue(), ^{
block(image, nil);
});
block(image, nil);
}];
}
- (void)cancelDownload:(id)downloadToken
{
if (downloadToken) {
dispatch_block_t block = (id)downloadToken;
block();
((dispatch_block_t)downloadToken)();
}
}

View File

@ -19,7 +19,8 @@
#import "RCTImageDownloader.h"
#import "RCTLog.h"
NSError *errorWithMessage(NSString *message) {
NSError *errorWithMessage(NSString *message)
{
NSDictionary *errorInfo = @{NSLocalizedDescriptionKey: message};
NSError *error = [[NSError alloc] initWithDomain:RCTErrorDomain code:0 userInfo:errorInfo];
return error;

View File

@ -61,23 +61,24 @@
if ([imageURL.pathExtension caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
_downloadToken = [_imageDownloader downloadDataForURL:imageURL block:^(NSData *data, NSError *error) {
if (data) {
CAKeyframeAnimation *animation = RCTGIFImageWithData(data);
CGImageRef firstFrame = (__bridge CGImageRef)animation.values.firstObject;
self.layer.bounds = CGRectMake(0, 0, CGImageGetWidth(firstFrame), CGImageGetHeight(firstFrame));
self.layer.contentsScale = 1.0;
self.layer.contentsGravity = kCAGravityResizeAspect;
self.layer.minificationFilter = kCAFilterLinear;
self.layer.magnificationFilter = kCAFilterLinear;
[self.layer addAnimation:animation forKey:@"contents"];
dispatch_async(dispatch_get_main_queue(), ^{
CAKeyframeAnimation *animation = RCTGIFImageWithData(data);
self.layer.contentsScale = 1.0;
self.layer.minificationFilter = kCAFilterLinear;
self.layer.magnificationFilter = kCAFilterLinear;
[self.layer addAnimation:animation forKey:@"contents"];
});
}
// TODO: handle errors
}];
} else {
_downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale() block:^(UIImage *image, NSError *error) {
if (image) {
[self.layer removeAnimationForKey:@"contents"];
self.layer.contentsScale = image.scale;
self.layer.contents = (__bridge id)image.CGImage;
dispatch_async(dispatch_get_main_queue(), ^{
[self.layer removeAnimationForKey:@"contents"];
self.layer.contentsScale = image.scale;
self.layer.contents = (__bridge id)image.CGImage;
});
}
// TODO: handle errors
}];

View File

@ -21,21 +21,86 @@ var _initialURL = RCTLinkingManager &&
var DEVICE_NOTIF_EVENT = 'openURL';
/**
* `LinkingIOS` gives you a general interface to interact with both, incoming
* and outgoing app links.
*
* ### Basic Usage
*
* #### Handling deep links
*
* If your app was launched from a external url registered to your app you can
* access and handle it from any component you want with
*
* ```
* componentDidMount() {
* var url = LinkingIOS.popInitialURL();
* }
* ```
*
* In case you also want to listen to incoming app links during your app's
* execution you'll need to add the following lines to you `*AppDelegate.m`:
*
* ```
* - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
* return [RCTLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
* }
* ```
*
* And then on your React component you'll be able to listen to the events on
* `LinkingIOS` as follows
*
* ```
* componentDidMount() {
* LinkingIOS.addEventListener('url', this._handleOpenURL);
* },
* componentWillUnmount() {
* LinkingIOS.removeEventListener('url', this._handleOpenURL);
* },
* _handleOpenURL(event) {
* console.log(event.url);
* }
* ```
*
* #### Triggering App links
*
* To trigger an app link (browser, email or custom schemas) you call
*
* ```
* LinkingIOS.openURL(url)
* ```
*
* If you want to check if any installed app can handle a given url beforehand you can call
* ```
* LinkingIOS.canOpenURL(url, (supported) => {
* if (!supported) {
* AlertIOS.alert('Can\'t handle url: ' + url);
* } else {
* LinkingIOS.openURL(url);
* }
* });
* ```
*/
class LinkingIOS {
static addEventListener(type, handler) {
/**
* Add a handler to LinkingIOS changes by listening to the `url` event type
* and providing the handler
*/
static addEventListener(type: string, handler: Function) {
invariant(
type === 'url',
'LinkingIOS only supports `url` events'
);
_notifHandlers[handler] = RCTDeviceEventEmitter.addListener(
DEVICE_NOTIF_EVENT,
(notifData) => {
handler(new LinkingIOS(notifData));
}
handler
);
}
static removeEventListener(type, handler) {
/**
* Remove a handler by passing the `url` event type and the handler
*/
static removeEventListener(type: string, handler: Function ) {
invariant(
type === 'url',
'LinkingIOS only supports `url` events'
@ -47,7 +112,10 @@ class LinkingIOS {
_notifHandlers[handler] = null;
}
static openURL(url) {
/**
* Try to open the given `url` with any of the installed apps.
*/
static openURL(url: string) {
invariant(
typeof url === 'string',
'Invalid url: should be a string'
@ -55,7 +123,11 @@ class LinkingIOS {
RCTLinkingManager.openURL(url);
}
static canOpenURL(url, callback) {
/**
* Determine wether or not the an installed app can handle a given `url`
* The callback function will be called with `bool supported` as the only argument
*/
static canOpenURL(url: string, callback: Function) {
invariant(
typeof url === 'string',
'Invalid url: should be a string'
@ -67,7 +139,11 @@ class LinkingIOS {
RCTLinkingManager.canOpenURL(url, callback);
}
static popInitialURL() {
/**
* If the app launch was triggered by an app link, it will pop the link url,
* otherwise it will return `null`
*/
static popInitialURL(): ?string {
var initialURL = _initialURL;
_initialURL = null;
return initialURL;

View File

@ -34,7 +34,7 @@ type ReachabilityStateIOS = $Enum<{
*
* ### reachabilityIOS
*
* Asynchronously determine if the device is online and on a cellular network.
* Asyncronously determine if the device is online and on a cellular network.
*
* - `none` - device is offline
* - `wifi` - device is online and connected via wifi, or is the iOS simulator

View File

@ -0,0 +1,70 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule Portal
* @flow
*/
'use strict';
var React = require('React');
var StyleSheet = require('StyleSheet');
var View = require('View');
var _portalRef: any;
/*
* A container that renders all the modals on top of everything else in the application.
*
* Portal makes it possible for application code to pass modal views all the way up to
* the root element created in `renderApplication`.
*
* Never use `<Portal>` in your code. There is only one Portal instance rendered
* by the top-level `renderApplication`.
*/
var Portal = React.createClass({
statics: {
showModal: function(component) {
if (!_portalRef) {
console.error('Calling showModal but no Portal has been rendered');
return;
}
_portalRef.setState({modal: component});
},
closeModal: function() {
if (!_portalRef) {
console.error('Calling closeModal but no Portal has been rendered');
return;
}
_portalRef.setState({modal: null});
},
},
getInitialState: function() {
return {modal: (null: any)};
},
render: function() {
_portalRef = this;
if (!this.state.modal) {
return <View />;
}
return (
<View style={styles.modalsContainer}>
{this.state.modal}
</View>
);
}
});
var styles = StyleSheet.create({
modalsContainer: {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
},
});
module.exports = Portal;

View File

@ -11,7 +11,10 @@
*/
'use strict';
var Portal = require('Portal');
var React = require('React');
var StyleSheet = require('StyleSheet');
var View = require('View');
var invariant = require('invariant');
@ -24,12 +27,27 @@ function renderApplication<D, P, S>(
rootTag,
'Expect to have a valid rootTag, instead got ', rootTag
);
React.render(
<RootComponent
{...initialProps}
/>,
React.render(
<View style={styles.appContainer}>
<RootComponent
{...initialProps}
/>
<Portal />
</View>,
rootTag
);
}
var styles = StyleSheet.create({
// This is needed so the application covers the whole screen
// and therefore the contents of the Portal are not clipped.
appContainer: {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
},
});
module.exports = renderApplication;

View File

@ -14,5 +14,6 @@
@property (nonatomic, copy) NSAttributedString *attributedText;
@property (nonatomic, assign) NSLineBreakMode lineBreakMode;
@property (nonatomic, assign) NSUInteger numberOfLines;
@property (nonatomic, assign) UIEdgeInsets contentInset;
@end

View File

@ -72,20 +72,26 @@
[self setNeedsDisplay];
}
- (CGRect)textFrame
{
return UIEdgeInsetsInsetRect(self.bounds, _contentInset);
}
- (void)layoutSubviews
{
[super layoutSubviews];
// The header comment for `size` says that a height of 0.0 should be enough,
// but it isn't.
_textContainer.size = CGSizeMake(self.bounds.size.width, CGFLOAT_MAX);
_textContainer.size = CGSizeMake([self textFrame].size.width, CGFLOAT_MAX);
}
- (void)drawRect:(CGRect)rect
{
CGPoint origin = [self textFrame].origin;
NSRange glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
[_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:CGPointZero];
[_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:CGPointZero];
[_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:origin];
[_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:origin];
}
- (NSNumber *)reactTagAtPoint:(CGPoint)point

View File

@ -124,4 +124,13 @@ RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
};
}
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView
{
NSNumber *reactTag = shadowView.reactTag;
UIEdgeInsets padding = shadowView.paddingAsInsets;
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
((RCTText *)viewRegistry[reactTag]).contentInset = padding;
};
}
@end

View File

@ -69,6 +69,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
TestUtils: undefined,
batchedUpdates: require('ReactUpdates').batchedUpdates,
cloneWithProps: require('cloneWithProps'),
createFragment: require('ReactFragment').create,
update: require('update'),
},
});

View File

@ -1,93 +1,78 @@
Pod::Spec.new do |s|
s.name = "React"
s.version = "0.3.1"
s.summary = "Build high quality mobile apps using React."
s.description = <<-DESC
React Native apps are built using the React JS
framework, and render directly to native UIKit
elements using a fully asynchronous architecture.
There is no browser and no HTML. We have picked what
we think is the best set of features from these and
other technologies to build what we hope to become
the best product development framework available,
with an emphasis on iteration speed, developer
delight, continuity of technology, and absolutely
beautiful and fast products with no compromises in
quality or capability.
DESC
s.homepage = "http://facebook.github.io/react-native/"
s.license = "BSD"
s.author = "Facebook"
s.source = { :git => "https://github.com/facebook/react-native.git", :tag => "v#{s.version}" }
s.default_subspec = 'Core'
s.requires_arc = true
s.platform = :ios, "7.0"
s.prepare_command = 'npm install'
s.preserve_paths = "cli.js", "Libraries/**/*.js", "lint", "linter.js", "node_modules", "package.json", "packager", "PATENTS", "react-native-cli"
s.subspec 'Core' do |ss|
ss.source_files = "React/**/*.{c,h,m}"
ss.exclude_files = "**/__tests__/*", "IntegrationTests/*"
ss.frameworks = "JavaScriptCore"
end
s.name = "React"
s.version = "0.1.0"
s.summary = "Build high quality mobile apps using React."
s.description= <<-DESC
React Native apps are built using the React JS framework,
and render directly to native UIKit elements using a fully
asynchronous architecture. There is no browser and no HTML.
We have picked what we think is the best set of features from
these and other technologies to build what we hope to become
the best product development framework available, with an
emphasis on iteration speed, developer delight, continuity
of technology, and absolutely beautiful and fast products
with no compromises in quality or capability.
DESC
s.homepage = "http://facebook.github.io/react-native/"
s.license = "BSD"
s.author = "Facebook"
s.platform = :ios, "7.0"
s.source = { :git => "https://github.com/facebook/react-native.git", :tag => "v#{s.version}" }
s.source_files = "React/**/*.{c,h,m}"
s.resources = "Resources/*.png"
s.preserve_paths = "cli.js", "Libraries/**/*.js", "lint", "linter.js", "node_modules", "package.json", "packager", "PATENTS", "react-native-cli"
s.exclude_files = "**/__tests__/*", "IntegrationTests/*"
s.frameworks = "JavaScriptCore"
s.requires_arc = true
s.prepare_command = 'npm install'
s.libraries = 'icucore'
s.subspec 'RCTActionSheet' do |ss|
ss.dependency 'React/Core'
ss.source_files = "Libraries/ActionSheetIOS/*.{h,m}"
ss.source_files = "Libraries/ActionSheetIOS/*.{h,m}"
ss.preserve_paths = "Libraries/ActionSheetIOS/*.js"
end
s.subspec 'RCTAdSupport' do |ss|
ss.dependency 'React/Core'
ss.source_files = "Libraries/AdSupport/*.{h,m}"
ss.preserve_paths = "Libraries/AdSupport/*.js"
ss.source_files = "Libraries/RCTAdSupport/*.{h,m}"
ss.preserve_paths = "Libraries/RCTAdSupport/*.js"
end
s.subspec 'RCTAnimation' do |ss|
ss.dependency 'React/Core'
ss.source_files = "Libraries/Animation/*.{h,m}"
ss.preserve_paths = "Libraries/Animation/*.js"
ss.source_files = "Libraries/Animation/*.{h,m}"
ss.preserve_paths = "Libraries/Animation/*.js"
end
s.subspec 'RCTGeolocation' do |ss|
ss.dependency 'React/Core'
ss.source_files = "Libraries/Geolocation/*.{h,m}"
ss.preserve_paths = "Libraries/Geolocation/*.js"
ss.source_files = "Libraries/Geolocation/*.{h,m}"
ss.preserve_paths = "Libraries/Geolocation/*.js"
end
s.subspec 'RCTImage' do |ss|
ss.dependency 'React/Core'
ss.source_files = "Libraries/Image/*.{h,m}"
ss.preserve_paths = "Libraries/Image/*.js"
ss.source_files = "Libraries/Image/*.{h,m}"
ss.preserve_paths = "Libraries/Image/*.js"
end
s.subspec 'RCTNetwork' do |ss|
ss.dependency 'React/Core'
ss.source_files = "Libraries/Network/*.{h,m}"
ss.preserve_paths = "Libraries/Network/*.js"
ss.source_files = "Libraries/Network/*.{h,m}"
ss.preserve_paths = "Libraries/Network/*.js"
end
s.subspec 'RCTPushNotification' do |ss|
ss.dependency 'React/Core'
ss.source_files = "Libraries/PushNotificationIOS/*.{h,m}"
ss.preserve_paths = "Libraries/PushNotificationIOS/*.js"
ss.source_files = "Libraries/PushNotificationIOS/*.{h,m}"
ss.preserve_paths = "Libraries/PushNotificationIOS/*.js"
end
s.subspec 'RCTWebSocketDebugger' do |ss|
ss.libraries = 'icucore'
ss.dependency 'React/Core'
ss.source_files = "Libraries/RCTWebSocketDebugger/*.{h,m}"
ss.source_files = "Libraries/RCTWebSocketDebugger/*.{h,m}"
end
s.subspec 'RCTText' do |ss|
ss.dependency 'React/Core'
ss.source_files = "Libraries/Text/*.{h,m}"
ss.preserve_paths = "Libraries/Text/*.js"
ss.source_files = "Libraries/Text/*.{h,m}"
ss.preserve_paths = "Libraries/Text/*.js"
end
s.subspec 'RCTVibration' do |ss|
ss.dependency 'React/Core'
ss.source_files = "Libraries/Vibration/*.{h,m}"
ss.preserve_paths = "Libraries/Vibration/*.js"
ss.source_files = "Libraries/Vibration/*.{h,m}"
ss.preserve_paths = "Libraries/Vibration/*.js"
end
end

View File

@ -52,6 +52,7 @@
+ (UITextFieldViewMode)UITextFieldViewMode:(id)json;
+ (UIScrollViewKeyboardDismissMode)UIScrollViewKeyboardDismissMode:(id)json;
+ (UIKeyboardType)UIKeyboardType:(id)json;
+ (UIReturnKeyType)UIReturnKeyType:(id)json;
+ (UIViewContentMode)UIViewContentMode:(id)json;
+ (UIBarStyle)UIBarStyle:(id)json;
@ -123,10 +124,17 @@ BOOL RCTCopyProperty(id target, id source, NSString *keyPath);
}
#endif
/**
* This macro is used for creating simple converter functions that just call
* the specified getter method on the json value.
*/
#define RCT_CONVERTER(type, name, getter) \
RCT_CUSTOM_CONVERTER(type, name, [json getter])
/**
* This macro is used for creating converter functions with arbitrary logic.
*/
#define RCT_CONVERTER_CUSTOM(type, name, code) \
#define RCT_CUSTOM_CONVERTER(type, name, code) \
+ (type)name:(id)json \
{ \
if (json == [NSNull null]) { \
@ -143,20 +151,13 @@ BOOL RCTCopyProperty(id target, id source, NSString *keyPath);
} \
}
/**
* This macro is used for creating simple converter functions that just call
* the specified getter method on the json value.
*/
#define RCT_CONVERTER(type, name, getter) \
RCT_CONVERTER_CUSTOM(type, name, [json getter])
/**
* This macro is similar to RCT_CONVERTER, but specifically geared towards
* numeric types. It will handle string input correctly, and provides more
* detailed error reporting if a wrong value is passed in.
*/
#define RCT_NUMBER_CONVERTER(type, getter) \
RCT_CONVERTER_CUSTOM(type, type, [[self NSNumber:json] getter])
RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter])
/**
* This macro is used for creating converters for enum types.
@ -189,57 +190,6 @@ RCT_CONVERTER_CUSTOM(type, type, [[self NSNumber:json] getter])
return value ? [value getter] : default; \
}
/**
* This macro is used for creating converter functions for structs that consist
* of a number of CGFloat properties, such as CGPoint, CGRect, etc.
*/
#define RCT_CGSTRUCT_CONVERTER(type, values, _aliases) \
+ (type)type:(id)json \
{ \
@try { \
static NSArray *fields; \
static NSUInteger count; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
fields = values; \
count = [fields count]; \
}); \
type result; \
if ([json isKindOfClass:[NSArray class]]) { \
if ([json count] != count) { \
RCTLogError(@"Expected array with count %zd, but count is %zd: %@", count, [json count], json); \
} else { \
for (NSUInteger i = 0; i < count; i++) { \
((CGFloat *)&result)[i] = [self CGFloat:json[i]]; \
} \
} \
} else if ([json isKindOfClass:[NSDictionary class]]) { \
NSDictionary *aliases = _aliases; \
if (aliases.count) { \
json = [json mutableCopy]; \
for (NSString *alias in aliases) { \
NSString *key = aliases[alias]; \
NSNumber *number = json[key]; \
if (number) { \
((NSMutableDictionary *)json)[key] = number; \
} \
} \
} \
for (NSUInteger i = 0; i < count; i++) { \
((CGFloat *)&result)[i] = [self CGFloat:json[fields[i]]]; \
} \
} else if (json && json != [NSNull null]) { \
RCTLogError(@"Expected NSArray or NSDictionary for %s, received %@: %@", #type, [json class], json); \
} \
return result; \
} \
@catch (__unused NSException *e) { \
RCTLogError(@"JSON value '%@' cannot be converted to '%s'", json, #type); \
type result; \
return result; \
} \
}
/**
* This macro is used for creating converter functions for typed arrays.
*/

View File

@ -24,8 +24,8 @@ RCT_NUMBER_CONVERTER(uint64_t, unsignedLongLongValue);
RCT_NUMBER_CONVERTER(NSInteger, integerValue)
RCT_NUMBER_CONVERTER(NSUInteger, unsignedIntegerValue)
RCT_CONVERTER_CUSTOM(NSArray *, NSArray, [NSArray arrayWithArray:json])
RCT_CONVERTER_CUSTOM(NSDictionary *, NSDictionary, [NSDictionary dictionaryWithDictionary:json])
RCT_CUSTOM_CONVERTER(NSArray *, NSArray, [NSArray arrayWithArray:json])
RCT_CUSTOM_CONVERTER(NSDictionary *, NSDictionary, [NSDictionary dictionaryWithDictionary:json])
RCT_CONVERTER(NSString *, NSString, description)
+ (NSNumber *)NSNumber:(id)json
@ -33,7 +33,12 @@ RCT_CONVERTER(NSString *, NSString, description)
if ([json isKindOfClass:[NSNumber class]]) {
return json;
} else if ([json isKindOfClass:[NSString class]]) {
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
static NSNumberFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [[NSNumberFormatter alloc] init];
formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
});
NSNumber *number = [formatter numberFromString:json];
if (!number) {
RCTLogError(@"JSON String '%@' could not be interpreted as a number", json);
@ -74,12 +79,35 @@ RCT_CONVERTER(NSString *, NSString, description)
return [NSURLRequest requestWithURL:[self NSURL:json]];
}
+ (NSDate *)NSDate:(id)json
{
if ([json isKindOfClass:[NSNumber class]]) {
return [NSDate dateWithTimeIntervalSince1970:[self NSTimeInterval:json]];
} else if ([json isKindOfClass:[NSString class]]) {
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ";
formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
formatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
});
NSDate *date = [formatter dateFromString:json];
if (!date) {
RCTLogError(@"JSON String '%@' could not be interpreted as a date. Expected format: YYYY-MM-DD'T'HH:mm:ss.sssZ", json);
}
return date;
} else if (json && json != [NSNull null]) {
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a date", json, [json class]);
}
return nil;
}
// JS Standard for time is milliseconds
RCT_CONVERTER_CUSTOM(NSDate *, NSDate, [NSDate dateWithTimeIntervalSince1970:[self double:json] / 1000.0])
RCT_CONVERTER_CUSTOM(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0)
RCT_CUSTOM_CONVERTER(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0)
// JS standard for time zones is minutes.
RCT_CONVERTER_CUSTOM(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0])
RCT_CUSTOM_CONVERTER(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0])
RCT_ENUM_CONVERTER(NSTextAlignment, (@{
@"auto": @(NSTextAlignmentNatural),
@ -116,10 +144,33 @@ RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{
}), UIScrollViewKeyboardDismissModeNone, integerValue)
RCT_ENUM_CONVERTER(UIKeyboardType, (@{
@"numeric": @(UIKeyboardTypeDecimalPad),
@"default": @(UIKeyboardTypeDefault),
@"ascii-capable": @(UIKeyboardTypeASCIICapable),
@"numbers-and-punctuation": @(UIKeyboardTypeNumbersAndPunctuation),
@"url": @(UIKeyboardTypeURL),
@"number-pad": @(UIKeyboardTypeNumberPad),
@"phone-pad": @(UIKeyboardTypePhonePad),
@"name-phone-pad": @(UIKeyboardTypeNamePhonePad),
@"email-address": @(UIKeyboardTypeEmailAddress),
@"decimal-pad": @(UIKeyboardTypeDecimalPad),
@"twitter": @(UIKeyboardTypeTwitter),
@"web-search": @(UIKeyboardTypeWebSearch),
}), UIKeyboardTypeDefault, integerValue)
RCT_ENUM_CONVERTER(UIReturnKeyType, (@{
@"default": @(UIReturnKeyDefault),
@"go": @(UIReturnKeyGo),
@"google": @(UIReturnKeyGoogle),
@"join": @(UIReturnKeyJoin),
@"next": @(UIReturnKeyNext),
@"route": @(UIReturnKeyRoute),
@"search": @(UIReturnKeySearch),
@"send": @(UIReturnKeySend),
@"yahoo": @(UIReturnKeyYahoo),
@"done": @(UIReturnKeyDone),
@"emergency-call": @(UIReturnKeyEmergencyCall),
}), UIReturnKeyDefault, integerValue)
RCT_ENUM_CONVERTER(UIViewContentMode, (@{
@"scale-to-fill": @(UIViewContentModeScaleToFill),
@"scale-aspect-fit": @(UIViewContentModeScaleAspectFit),
@ -142,7 +193,58 @@ RCT_ENUM_CONVERTER(UIBarStyle, (@{
}), UIBarStyleDefault, integerValue)
// TODO: normalise the use of w/width so we can do away with the alias values (#6566645)
RCT_CONVERTER_CUSTOM(CGFloat, CGFloat, [self double:json])
/**
* This macro is used for creating converter functions for structs that consist
* of a number of CGFloat properties, such as CGPoint, CGRect, etc.
*/
#define RCT_CGSTRUCT_CONVERTER(type, values, _aliases) \
+ (type)type:(id)json \
{ \
@try { \
static NSArray *fields; \
static NSUInteger count; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
fields = values; \
count = [fields count]; \
}); \
type result; \
if ([json isKindOfClass:[NSArray class]]) { \
if ([json count] != count) { \
RCTLogError(@"Expected array with count %zd, but count is %zd: %@", count, [json count], json); \
} else { \
for (NSUInteger i = 0; i < count; i++) { \
((CGFloat *)&result)[i] = [self CGFloat:json[i]]; \
} \
} \
} else if ([json isKindOfClass:[NSDictionary class]]) { \
NSDictionary *aliases = _aliases; \
if (aliases.count) { \
json = [json mutableCopy]; \
for (NSString *alias in aliases) { \
NSString *key = aliases[alias]; \
NSNumber *number = json[key]; \
if (number) { \
((NSMutableDictionary *)json)[key] = number; \
} \
} \
} \
for (NSUInteger i = 0; i < count; i++) { \
((CGFloat *)&result)[i] = [self CGFloat:json[fields[i]]]; \
} \
} else if (json && json != [NSNull null]) { \
RCTLogError(@"Expected NSArray or NSDictionary for %s, received %@: %@", #type, [json class], json); \
} \
return result; \
} \
@catch (__unused NSException *e) { \
RCTLogError(@"JSON value '%@' cannot be converted to '%s'", json, #type); \
type result; \
return result; \
} \
}
RCT_CUSTOM_CONVERTER(CGFloat, CGFloat, [self double:json])
RCT_CGSTRUCT_CONVERTER(CGPoint, (@[@"x", @"y"]), nil)
RCT_CGSTRUCT_CONVERTER(CGSize, (@[@"width", @"height"]), (@{@"w": @"width", @"h": @"height"}))
RCT_CGSTRUCT_CONVERTER(CGRect, (@[@"x", @"y", @"width", @"height"]), (@{@"w": @"width", @"h": @"height"}))
@ -701,25 +803,32 @@ BOOL RCTSetProperty(id target, NSString *keyPath, SEL type, id json)
return NO;
}
// Get converted value
NSMethodSignature *signature = [RCTConvert methodSignatureForSelector:type];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&type atIndex:1];
[invocation setArgument:&json atIndex:2];
[invocation invokeWithTarget:[RCTConvert class]];
NSUInteger length = [signature methodReturnLength];
void *value = malloc(length);
[invocation getReturnValue:value];
@try {
// Get converted value
NSMethodSignature *signature = [RCTConvert methodSignatureForSelector:type];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&type atIndex:1];
[invocation setArgument:&json atIndex:2];
[invocation invokeWithTarget:[RCTConvert class]];
NSUInteger length = [signature methodReturnLength];
void *value = malloc(length);
[invocation getReturnValue:value];
// Set converted value
signature = [target methodSignatureForSelector:setter];
invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&setter atIndex:1];
[invocation setArgument:value atIndex:2];
[invocation invokeWithTarget:target];
free(value);
// Set converted value
signature = [target methodSignatureForSelector:setter];
invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&setter atIndex:1];
[invocation setArgument:value atIndex:2];
[invocation invokeWithTarget:target];
free(value);
return YES;
return YES;
}
@catch (NSException *exception) {
RCTLogError(@"Exception thrown while attempting to set property '%@' of \
'%@' with value '%@': %@", key, [target class], json, exception);
return NO;
}
}
BOOL RCTCopyProperty(id target, id source, NSString *keyPath)
@ -748,7 +857,7 @@ BOOL RCTCopyProperty(id target, id source, NSString *keyPath)
return NO;
}
// Get converted value
// Get value
NSMethodSignature *signature = [source methodSignatureForSelector:getter];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&getter atIndex:1];
@ -757,7 +866,7 @@ BOOL RCTCopyProperty(id target, id source, NSString *keyPath)
void *value = malloc(length);
[invocation getReturnValue:value];
// Set converted value
// Set value
signature = [target methodSignatureForSelector:setter];
invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&setter atIndex:1];

View File

@ -80,10 +80,18 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio
if ((self = [super init])) {
_property = [RCTConvert NSString:config[@"property"]];
// TODO: this should be provided in ms, not seconds
// (this will require changing all call sites to ms as well)
_duration = [RCTConvert NSTimeInterval:config[@"duration"]] * 1000.0 ?: duration;
_delay = [RCTConvert NSTimeInterval:config[@"delay"]] * 1000.0;
_duration = [RCTConvert NSTimeInterval:config[@"duration"]] ?: duration;
if (_duration > 0.0 && _duration < 0.01) {
RCTLogError(@"RCTLayoutAnimation expects timings to be in ms, not seconds.");
_duration = _duration * 1000.0;
}
_delay = [RCTConvert NSTimeInterval:config[@"delay"]];
if (_delay > 0.0 && _delay < 0.01) {
RCTLogError(@"RCTLayoutAnimation expects timings to be in ms, not seconds.");
_delay = _delay * 1000.0;
}
_animationType = [RCTConvert RCTAnimationType:config[@"type"]];
if (_animationType == RCTAnimationTypeSpring) {
_springDamping = [RCTConvert CGFloat:config[@"springDamping"]];
@ -142,9 +150,11 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio
if ((self = [super init])) {
// TODO: this should be provided in ms, not seconds
// (this will require changing all call sites to ms as well)
NSTimeInterval duration = [RCTConvert NSTimeInterval:config[@"duration"]] * 1000.0;
NSTimeInterval duration = [RCTConvert NSTimeInterval:config[@"duration"]];
if (duration > 0.0 && duration < 0.01) {
RCTLogError(@"RCTLayoutAnimation expects timings to be in ms, not seconds.");
duration = duration * 1000.0;
}
_createAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"create"]];
_updateAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"update"]];
@ -1291,6 +1301,32 @@ static void RCTMeasureLayout(RCTShadowView *view,
@"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),

View File

@ -43,7 +43,7 @@
@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
@property (nonatomic, assign) NSUInteger throttleScrollCallbackMS;
@property (nonatomic, assign) NSTimeInterval scrollEventThrottle;
@property (nonatomic, assign) BOOL centerContent;
@property (nonatomic, copy) NSArray *stickyHeaderIndices;

View File

@ -274,7 +274,7 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
_contentInset = UIEdgeInsetsZero;
_contentSize = CGSizeZero;
_throttleScrollCallbackMS = 0;
_scrollEventThrottle = 0.0;
_lastScrollDispatchTime = CACurrentMediaTime();
_cachedChildFrames = [[NSMutableArray alloc] init];
@ -393,16 +393,15 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove)
[self updateClippedSubviews];
NSTimeInterval now = CACurrentMediaTime();
NSTimeInterval throttleScrollCallbackSeconds = _throttleScrollCallbackMS / 1000.0;
/**
* TODO: this logic looks wrong, and it may be because it is. Currently, if _throttleScrollCallbackMS
* TODO: this logic looks wrong, and it may be because it is. Currently, if _scrollEventThrottle
* is set to zero (the default), the "didScroll" event is only sent once per scroll, instead of repeatedly
* while scrolling as expected. However, if you "fix" that bug, ScrollView will generate repeated
* warnings, and behave strangely (ListView works fine however), so don't fix it unless you fix that too!
*/
if (_allowNextScrollNoMatterWhat ||
(_throttleScrollCallbackMS != 0 && throttleScrollCallbackSeconds < (now - _lastScrollDispatchTime))) {
(_scrollEventThrottle > 0 && _scrollEventThrottle < (now - _lastScrollDispatchTime))) {
// Calculate changed frames
NSMutableArray *updatedChildFrames = [[NSMutableArray alloc] init];

View File

@ -39,12 +39,14 @@ RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(scrollsToTop, BOOL)
RCT_EXPORT_VIEW_PROPERTY(showsHorizontalScrollIndicator, BOOL)
RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL)
RCT_EXPORT_VIEW_PROPERTY(stickyHeaderIndices, NSNumberArray);
RCT_EXPORT_VIEW_PROPERTY(throttleScrollCallbackMS, double);
RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat);
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets);
RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets);
RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint);
RCT_EXPORT_VIEW_PROPERTY(stickyHeaderIndices, NSNumberArray)
RCT_EXPORT_VIEW_PROPERTY(scrollEventThrottle, NSTimeInterval)
RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets)
RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint)
RCT_DEPRECATED_VIEW_PROPERTY(throttleScrollCallbackMS, scrollEventThrottle)
- (NSDictionary *)constantsToExport
{

View File

@ -15,7 +15,7 @@
@property (nonatomic, assign) BOOL caretHidden;
@property (nonatomic, assign) BOOL autoCorrect;
@property (nonatomic, assign) UIEdgeInsets paddingEdgeInsets; // TODO: contentInset
@property (nonatomic, assign) UIEdgeInsets contentInset;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;

View File

@ -31,7 +31,6 @@
[self addTarget:self action:@selector(_textFieldEndEditing) forControlEvents:UIControlEventEditingDidEnd];
[self addTarget:self action:@selector(_textFieldSubmitEditing) forControlEvents:UIControlEventEditingDidEndOnExit];
_reactSubviews = [[NSMutableArray alloc] init];
self.returnKeyType = UIReturnKeyDone;
}
return self;
}
@ -71,7 +70,7 @@
- (CGRect)textRectForBounds:(CGRect)bounds
{
CGRect rect = [super textRectForBounds:bounds];
return UIEdgeInsetsInsetRect(rect, _paddingEdgeInsets);
return UIEdgeInsetsInsetRect(rect, _contentInset);
}
- (CGRect)editingRectForBounds:(CGRect)bounds

View File

@ -29,6 +29,9 @@ RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)
RCT_EXPORT_VIEW_PROPERTY(text, NSString)
RCT_EXPORT_VIEW_PROPERTY(clearButtonMode, UITextFieldViewMode)
RCT_EXPORT_VIEW_PROPERTY(keyboardType, UIKeyboardType)
RCT_EXPORT_VIEW_PROPERTY(returnKeyType, UIReturnKeyType)
RCT_EXPORT_VIEW_PROPERTY(enablesReturnKeyAutomatically, BOOL)
RCT_EXPORT_VIEW_PROPERTY(secureTextEntry, BOOL)
RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor)
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType, UITextAutocapitalizationType)
RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextField)
@ -53,7 +56,7 @@ RCT_CUSTOM_VIEW_PROPERTY(fontFamily, NSString, RCTTextField)
NSNumber *reactTag = shadowView.reactTag;
UIEdgeInsets padding = shadowView.paddingAsInsets;
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
((RCTTextField *)viewRegistry[reactTag]).paddingEdgeInsets = padding;
((RCTTextField *)viewRegistry[reactTag]).contentInset = padding;
};
}

View File

@ -153,4 +153,19 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
#define RCT_IGNORE_SHADOW_PROPERTY(name) \
- (void)set_##name:(id)value forShadowView:(id)view withDefaultView:(id)defaultView {}
/**
* Used for when view property names change. Will log an error when used.
*/
#define RCT_DEPRECATED_VIEW_PROPERTY(oldName, newName) \
- (void)set_##oldName:(id)json forView:(id)view withDefaultView:(id)defaultView { \
RCTLogError(@"Property '%s' has been replaced by '%s'.", #oldName, #newName); \
[self set_##newName:json forView:view withDefaultView:defaultView]; \
}
#define RCT_DEPRECATED_SHADOW_PROPERTY(oldName, newName) \
- (void)set_##oldName:(id)json forShadowView:(id)view withDefaultView:(id)defaultView { \
RCTLogError(@"Property '%s' has been replaced by '%s'.", #oldName, #newName); \
[self set_##newName:json forView:view withDefaultView:defaultView]; \
}
@end

View File

@ -1,6 +1,6 @@
{
"name": "react-native",
"version": "0.3.1",
"version": "0.3.0",
"description": "A framework for building native apps using React",
"repository": {
"type": "git",

View File

@ -713,6 +713,6 @@ function NotFoundError() {
this.status = 404;
}
NotFoundError.__proto__ = Error.prototype;
util.inherits(NotFoundError, Error);
module.exports = DependecyGraph;