Updates from Wed 29 Apr

This commit is contained in:
Ben Alpert 2015-04-29 18:55:07 -07:00
commit d8ab648fce
127 changed files with 5138 additions and 761 deletions

View File

@ -29,3 +29,6 @@ Examples/UIExplorer/ImageMocks.js
[options]
module.system=haste
[version]
0.10.0

View File

@ -57,6 +57,17 @@ var styles = StyleSheet.create({
borderLeftWidth: 40,
borderLeftColor: 'blue',
},
border5: {
borderRadius: 50,
borderTopWidth: 10,
borderTopColor: 'red',
borderRightWidth: 20,
borderRightColor: 'yellow',
borderBottomWidth: 30,
borderBottomColor: 'green',
borderLeftWidth: 40,
borderLeftColor: 'blue',
},
});
exports.title = 'Border';
@ -71,7 +82,7 @@ exports.examples = [
},
{
title: 'Equal-Width / Same-Color',
description: 'borderWidth & borderColor',
description: 'borderWidth & borderColor & borderRadius',
render() {
return <View style={[styles.box, styles.borderRadius]} />;
}
@ -97,4 +108,11 @@ exports.examples = [
return <View style={[styles.box, styles.border4]} />;
}
},
{
title: 'Custom Borders',
description: 'border*Width & border*Color',
render() {
return <View style={[styles.box, styles.border5]} />;
}
},
];

View File

@ -50,7 +50,8 @@ var GeolocationExample = React.createClass({
componentDidMount: function() {
navigator.geolocation.getCurrentPosition(
(initialPosition) => this.setState({initialPosition}),
(error) => console.error(error)
(error) => console.error(error),
{enableHighAccuracy: true, timeout: 100, maximumAge: 1000}
);
this.watchID = navigator.geolocation.watchPosition((lastPosition) => {
this.setState({lastPosition});

View File

@ -0,0 +1,169 @@
/**
* 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 {
SegmentedControlIOS,
Text,
View,
StyleSheet
} = React;
var BasicSegmentedControlExample = React.createClass({
render() {
return (
<View>
<View style={{marginBottom: 10}}>
<SegmentedControlIOS values={['One', 'Two']} />
</View>
<View>
<SegmentedControlIOS values={['One', 'Two', 'Three', 'Four', 'Five']} />
</View>
</View>
);
}
});
var PreSelectedSegmentedControlExample = React.createClass({
render() {
return (
<View>
<View>
<SegmentedControlIOS values={['One', 'Two']} selectedIndex={0} />
</View>
</View>
);
}
});
var MomentarySegmentedControlExample = React.createClass({
render() {
return (
<View>
<View>
<SegmentedControlIOS values={['One', 'Two']} momentary={true} />
</View>
</View>
);
}
});
var DisabledSegmentedControlExample = React.createClass({
render() {
return (
<View>
<View>
<SegmentedControlIOS enabled={false} values={['One', 'Two']} selectedIndex={1} />
</View>
</View>
);
},
});
var ColorSegmentedControlExample = React.createClass({
render() {
return (
<View>
<View style={{marginBottom: 10}}>
<SegmentedControlIOS tintColor="#ff0000" values={['One', 'Two', 'Three', 'Four']} selectedIndex={0} />
</View>
<View>
<SegmentedControlIOS tintColor="#00ff00" values={['One', 'Two', 'Three']} selectedIndex={1} />
</View>
</View>
);
},
});
var EventSegmentedControlExample = React.createClass({
getInitialState() {
return {
values: ['One', 'Two', 'Three'],
value: 'Not selected',
selectedIndex: undefined
};
},
render() {
return (
<View>
<Text style={styles.text} >
Value: {this.state.value}
</Text>
<Text style={styles.text} >
Index: {this.state.selectedIndex}
</Text>
<SegmentedControlIOS
values={this.state.values}
selectedIndex={this.state.selectedIndex}
onChange={this._onChange}
onValueChange={this._onValueChange} />
</View>
);
},
_onChange(event) {
this.setState({
selectedIndex: event.nativeEvent.selectedIndex,
});
},
_onValueChange(value) {
this.setState({
value: value,
});
}
});
var styles = StyleSheet.create({
text: {
fontSize: 14,
textAlign: 'center',
fontWeight: '500',
margin: 10,
},
});
exports.title = '<SegmentedControlIOS>';
exports.displayName = 'SegmentedControlExample';
exports.description = 'Native segmented control';
exports.examples = [
{
title: 'Segmented controls can have values',
render(): ReactElement { return <BasicSegmentedControlExample />; }
},
{
title: 'Segmented controls can have a pre-selected value',
render(): ReactElement { return <PreSelectedSegmentedControlExample />; }
},
{
title: 'Segmented controls can be momentary',
render(): ReactElement { return <MomentarySegmentedControlExample />; }
},
{
title: 'Segmented controls can be disabled',
render(): ReactElement { return <DisabledSegmentedControlExample />; }
},
{
title: 'Custom colors can be provided',
render(): ReactElement { return <ColorSegmentedControlExample />; }
},
{
title: 'Change events can be detected',
render(): ReactElement { return <EventSegmentedControlExample />; }
}
];

View File

@ -88,9 +88,9 @@ var styles = StyleSheet.create({
height: 26,
borderWidth: 0.5,
borderColor: '#0f0f0f',
padding: 4,
flex: 1,
fontSize: 13,
padding: 4,
},
multiline: {
borderWidth: 0.5,
@ -98,6 +98,22 @@ var styles = StyleSheet.create({
flex: 1,
fontSize: 13,
height: 50,
padding: 4,
marginBottom: 4,
},
multilineWithFontStyles: {
color: 'blue',
fontWeight: 'bold',
fontSize: 18,
fontFamily: 'Cochin',
height: 60,
},
multilineChild: {
width: 50,
height: 40,
position: 'absolute',
right: 5,
backgroundColor: 'red',
},
eventLabel: {
margin: 3,
@ -118,7 +134,7 @@ var styles = StyleSheet.create({
});
exports.title = '<TextInput>';
exports.description = 'Single-line text inputs.';
exports.description = 'Single and multi-line text inputs.';
exports.examples = [
{
title: 'Auto-focus',
@ -313,7 +329,7 @@ exports.examples = [
},
{
title: 'Clear and select',
render: function () {
render: function() {
return (
<View>
<WithLabel label="clearTextOnFocus">
@ -336,4 +352,42 @@ exports.examples = [
);
}
},
{
title: 'Multiline',
render: function() {
return (
<View>
<TextInput
placeholder="multiline text input"
multiline={true}
style={styles.multiline}
/>
<TextInput
placeholder="mutliline text input with font styles and placeholder"
multiline={true}
clearTextOnFocus={true}
autoCorrect={true}
autoCapitalize="words"
placeholderTextColor="red"
keyboardType="url"
style={[styles.multiline, styles.multilineWithFontStyles]}
/>
<TextInput
placeholder="uneditable mutliline text input"
editable={false}
multiline={true}
style={styles.multiline}
/>
<TextInput
placeholder="multiline with children"
multiline={true}
enablesReturnKeyAutomatically={true}
returnKeyType="go"
style={styles.multiline}>
<View style={styles.multilineChild}/>
</TextInput>
</View>
)
}
}
];

View File

@ -70,6 +70,7 @@ var styles = StyleSheet.create({
},
titleContainer: {
borderWidth: 0.5,
borderRadius: 2.5,
borderColor: '#d6d7da',
backgroundColor: '#f6f7f8',
paddingHorizontal: 10,
@ -78,8 +79,10 @@ var styles = StyleSheet.create({
titleRow: {
flexDirection: 'row',
justifyContent: 'space-between',
backgroundColor: 'transparent',
},
titleText: {
backgroundColor: 'transparent',
fontSize: 14,
fontWeight: '500',
},
@ -97,6 +100,7 @@ var styles = StyleSheet.create({
height: 8,
},
children: {
backgroundColor: 'transparent',
padding: 10,
}
});

View File

@ -43,6 +43,7 @@ var COMPONENTS = [
require('./NavigatorIOSExample'),
require('./PickerIOSExample'),
require('./ScrollViewExample'),
require('./SegmentedControlIOSExample'),
require('./SliderIOSExample'),
require('./SwitchIOSExample'),
require('./TabBarIOSExample'),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 KiB

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View File

@ -94,6 +94,7 @@ var WebViewExample = React.createClass({
automaticallyAdjustContentInsets={false}
style={styles.webView}
url={this.state.url}
javaScriptEnabledAndroid={true}
onNavigationStateChange={this.onNavigationStateChange}
startInLoadingState={true}
/>

View File

@ -0,0 +1,371 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
0CF68B051AF0549300FF9E5C /* ARTGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68ADE1AF0549300FF9E5C /* ARTGroup.m */; };
0CF68B061AF0549300FF9E5C /* ARTNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE01AF0549300FF9E5C /* ARTNode.m */; };
0CF68B071AF0549300FF9E5C /* ARTRenderable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE21AF0549300FF9E5C /* ARTRenderable.m */; };
0CF68B081AF0549300FF9E5C /* ARTShape.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE41AF0549300FF9E5C /* ARTShape.m */; };
0CF68B091AF0549300FF9E5C /* ARTSurfaceView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE61AF0549300FF9E5C /* ARTSurfaceView.m */; };
0CF68B0A1AF0549300FF9E5C /* ARTText.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE81AF0549300FF9E5C /* ARTText.m */; };
0CF68B0B1AF0549300FF9E5C /* ARTBrush.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AEC1AF0549300FF9E5C /* ARTBrush.m */; };
0CF68B0C1AF0549300FF9E5C /* ARTLinearGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AEE1AF0549300FF9E5C /* ARTLinearGradient.m */; };
0CF68B0D1AF0549300FF9E5C /* ARTPattern.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF01AF0549300FF9E5C /* ARTPattern.m */; };
0CF68B0E1AF0549300FF9E5C /* ARTRadialGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF21AF0549300FF9E5C /* ARTRadialGradient.m */; };
0CF68B0F1AF0549300FF9E5C /* ARTSolidColor.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF41AF0549300FF9E5C /* ARTSolidColor.m */; };
0CF68B101AF0549300FF9E5C /* RCTConvert+ART.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF71AF0549300FF9E5C /* RCTConvert+ART.m */; };
0CF68B111AF0549300FF9E5C /* ARTGroupManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AFA1AF0549300FF9E5C /* ARTGroupManager.m */; };
0CF68B121AF0549300FF9E5C /* ARTNodeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AFC1AF0549300FF9E5C /* ARTNodeManager.m */; };
0CF68B131AF0549300FF9E5C /* ARTRenderableManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AFE1AF0549300FF9E5C /* ARTRenderableManager.m */; };
0CF68B141AF0549300FF9E5C /* ARTShapeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68B001AF0549300FF9E5C /* ARTShapeManager.m */; };
0CF68B151AF0549300FF9E5C /* ARTSurfaceViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68B021AF0549300FF9E5C /* ARTSurfaceViewManager.m */; };
0CF68B161AF0549300FF9E5C /* ARTTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68B041AF0549300FF9E5C /* ARTTextManager.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
0CF68ABF1AF0540F00FF9E5C /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
0CF68AC11AF0540F00FF9E5C /* libART.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libART.a; sourceTree = BUILT_PRODUCTS_DIR; };
0CF68ADB1AF0549300FF9E5C /* ARTCGFloatArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTCGFloatArray.h; sourceTree = "<group>"; };
0CF68ADC1AF0549300FF9E5C /* ARTContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTContainer.h; sourceTree = "<group>"; };
0CF68ADD1AF0549300FF9E5C /* ARTGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTGroup.h; sourceTree = "<group>"; };
0CF68ADE1AF0549300FF9E5C /* ARTGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTGroup.m; sourceTree = "<group>"; };
0CF68ADF1AF0549300FF9E5C /* ARTNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTNode.h; sourceTree = "<group>"; };
0CF68AE01AF0549300FF9E5C /* ARTNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTNode.m; sourceTree = "<group>"; };
0CF68AE11AF0549300FF9E5C /* ARTRenderable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTRenderable.h; sourceTree = "<group>"; };
0CF68AE21AF0549300FF9E5C /* ARTRenderable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTRenderable.m; sourceTree = "<group>"; };
0CF68AE31AF0549300FF9E5C /* ARTShape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTShape.h; sourceTree = "<group>"; };
0CF68AE41AF0549300FF9E5C /* ARTShape.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTShape.m; sourceTree = "<group>"; };
0CF68AE51AF0549300FF9E5C /* ARTSurfaceView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTSurfaceView.h; sourceTree = "<group>"; };
0CF68AE61AF0549300FF9E5C /* ARTSurfaceView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTSurfaceView.m; sourceTree = "<group>"; };
0CF68AE71AF0549300FF9E5C /* ARTText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTText.h; sourceTree = "<group>"; };
0CF68AE81AF0549300FF9E5C /* ARTText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTText.m; sourceTree = "<group>"; };
0CF68AE91AF0549300FF9E5C /* ARTTextFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTTextFrame.h; sourceTree = "<group>"; };
0CF68AEB1AF0549300FF9E5C /* ARTBrush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTBrush.h; sourceTree = "<group>"; };
0CF68AEC1AF0549300FF9E5C /* ARTBrush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTBrush.m; sourceTree = "<group>"; };
0CF68AED1AF0549300FF9E5C /* ARTLinearGradient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTLinearGradient.h; sourceTree = "<group>"; };
0CF68AEE1AF0549300FF9E5C /* ARTLinearGradient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTLinearGradient.m; sourceTree = "<group>"; };
0CF68AEF1AF0549300FF9E5C /* ARTPattern.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTPattern.h; sourceTree = "<group>"; };
0CF68AF01AF0549300FF9E5C /* ARTPattern.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPattern.m; sourceTree = "<group>"; };
0CF68AF11AF0549300FF9E5C /* ARTRadialGradient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTRadialGradient.h; sourceTree = "<group>"; };
0CF68AF21AF0549300FF9E5C /* ARTRadialGradient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTRadialGradient.m; sourceTree = "<group>"; };
0CF68AF31AF0549300FF9E5C /* ARTSolidColor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTSolidColor.h; sourceTree = "<group>"; };
0CF68AF41AF0549300FF9E5C /* ARTSolidColor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTSolidColor.m; sourceTree = "<group>"; };
0CF68AF61AF0549300FF9E5C /* RCTConvert+ART.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+ART.h"; sourceTree = "<group>"; };
0CF68AF71AF0549300FF9E5C /* RCTConvert+ART.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+ART.m"; sourceTree = "<group>"; };
0CF68AF91AF0549300FF9E5C /* ARTGroupManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTGroupManager.h; sourceTree = "<group>"; };
0CF68AFA1AF0549300FF9E5C /* ARTGroupManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTGroupManager.m; sourceTree = "<group>"; };
0CF68AFB1AF0549300FF9E5C /* ARTNodeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTNodeManager.h; sourceTree = "<group>"; };
0CF68AFC1AF0549300FF9E5C /* ARTNodeManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTNodeManager.m; sourceTree = "<group>"; };
0CF68AFD1AF0549300FF9E5C /* ARTRenderableManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTRenderableManager.h; sourceTree = "<group>"; };
0CF68AFE1AF0549300FF9E5C /* ARTRenderableManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTRenderableManager.m; sourceTree = "<group>"; };
0CF68AFF1AF0549300FF9E5C /* ARTShapeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTShapeManager.h; sourceTree = "<group>"; };
0CF68B001AF0549300FF9E5C /* ARTShapeManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTShapeManager.m; sourceTree = "<group>"; };
0CF68B011AF0549300FF9E5C /* ARTSurfaceViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTSurfaceViewManager.h; sourceTree = "<group>"; };
0CF68B021AF0549300FF9E5C /* ARTSurfaceViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTSurfaceViewManager.m; sourceTree = "<group>"; };
0CF68B031AF0549300FF9E5C /* ARTTextManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTTextManager.h; sourceTree = "<group>"; };
0CF68B041AF0549300FF9E5C /* ARTTextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTTextManager.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
0CF68ABE1AF0540F00FF9E5C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0CF68AB81AF0540F00FF9E5C = {
isa = PBXGroup;
children = (
0CF68ADB1AF0549300FF9E5C /* ARTCGFloatArray.h */,
0CF68ADC1AF0549300FF9E5C /* ARTContainer.h */,
0CF68ADD1AF0549300FF9E5C /* ARTGroup.h */,
0CF68ADE1AF0549300FF9E5C /* ARTGroup.m */,
0CF68ADF1AF0549300FF9E5C /* ARTNode.h */,
0CF68AE01AF0549300FF9E5C /* ARTNode.m */,
0CF68AE11AF0549300FF9E5C /* ARTRenderable.h */,
0CF68AE21AF0549300FF9E5C /* ARTRenderable.m */,
0CF68AE31AF0549300FF9E5C /* ARTShape.h */,
0CF68AE41AF0549300FF9E5C /* ARTShape.m */,
0CF68AE51AF0549300FF9E5C /* ARTSurfaceView.h */,
0CF68AE61AF0549300FF9E5C /* ARTSurfaceView.m */,
0CF68AE71AF0549300FF9E5C /* ARTText.h */,
0CF68AE81AF0549300FF9E5C /* ARTText.m */,
0CF68AE91AF0549300FF9E5C /* ARTTextFrame.h */,
0CF68AEA1AF0549300FF9E5C /* Brushes */,
0CF68AF61AF0549300FF9E5C /* RCTConvert+ART.h */,
0CF68AF71AF0549300FF9E5C /* RCTConvert+ART.m */,
0CF68AF81AF0549300FF9E5C /* ViewManagers */,
0CF68AC21AF0540F00FF9E5C /* Products */,
);
sourceTree = "<group>";
};
0CF68AC21AF0540F00FF9E5C /* Products */ = {
isa = PBXGroup;
children = (
0CF68AC11AF0540F00FF9E5C /* libART.a */,
);
name = Products;
sourceTree = "<group>";
};
0CF68AEA1AF0549300FF9E5C /* Brushes */ = {
isa = PBXGroup;
children = (
0CF68AEB1AF0549300FF9E5C /* ARTBrush.h */,
0CF68AEC1AF0549300FF9E5C /* ARTBrush.m */,
0CF68AED1AF0549300FF9E5C /* ARTLinearGradient.h */,
0CF68AEE1AF0549300FF9E5C /* ARTLinearGradient.m */,
0CF68AEF1AF0549300FF9E5C /* ARTPattern.h */,
0CF68AF01AF0549300FF9E5C /* ARTPattern.m */,
0CF68AF11AF0549300FF9E5C /* ARTRadialGradient.h */,
0CF68AF21AF0549300FF9E5C /* ARTRadialGradient.m */,
0CF68AF31AF0549300FF9E5C /* ARTSolidColor.h */,
0CF68AF41AF0549300FF9E5C /* ARTSolidColor.m */,
);
path = Brushes;
sourceTree = "<group>";
};
0CF68AF81AF0549300FF9E5C /* ViewManagers */ = {
isa = PBXGroup;
children = (
0CF68AF91AF0549300FF9E5C /* ARTGroupManager.h */,
0CF68AFA1AF0549300FF9E5C /* ARTGroupManager.m */,
0CF68AFB1AF0549300FF9E5C /* ARTNodeManager.h */,
0CF68AFC1AF0549300FF9E5C /* ARTNodeManager.m */,
0CF68AFD1AF0549300FF9E5C /* ARTRenderableManager.h */,
0CF68AFE1AF0549300FF9E5C /* ARTRenderableManager.m */,
0CF68AFF1AF0549300FF9E5C /* ARTShapeManager.h */,
0CF68B001AF0549300FF9E5C /* ARTShapeManager.m */,
0CF68B011AF0549300FF9E5C /* ARTSurfaceViewManager.h */,
0CF68B021AF0549300FF9E5C /* ARTSurfaceViewManager.m */,
0CF68B031AF0549300FF9E5C /* ARTTextManager.h */,
0CF68B041AF0549300FF9E5C /* ARTTextManager.m */,
);
path = ViewManagers;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
0CF68AC01AF0540F00FF9E5C /* ART */ = {
isa = PBXNativeTarget;
buildConfigurationList = 0CF68AD51AF0540F00FF9E5C /* Build configuration list for PBXNativeTarget "ART" */;
buildPhases = (
0CF68ABD1AF0540F00FF9E5C /* Sources */,
0CF68ABE1AF0540F00FF9E5C /* Frameworks */,
0CF68ABF1AF0540F00FF9E5C /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = ART;
productName = ART;
productReference = 0CF68AC11AF0540F00FF9E5C /* libART.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
0CF68AB91AF0540F00FF9E5C /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0620;
TargetAttributes = {
0CF68AC01AF0540F00FF9E5C = {
CreatedOnToolsVersion = 6.2;
};
};
};
buildConfigurationList = 0CF68ABC1AF0540F00FF9E5C /* Build configuration list for PBXProject "ART" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 0CF68AB81AF0540F00FF9E5C;
productRefGroup = 0CF68AC21AF0540F00FF9E5C /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
0CF68AC01AF0540F00FF9E5C /* ART */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
0CF68ABD1AF0540F00FF9E5C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0CF68B161AF0549300FF9E5C /* ARTTextManager.m in Sources */,
0CF68B111AF0549300FF9E5C /* ARTGroupManager.m in Sources */,
0CF68B0D1AF0549300FF9E5C /* ARTPattern.m in Sources */,
0CF68B0A1AF0549300FF9E5C /* ARTText.m in Sources */,
0CF68B121AF0549300FF9E5C /* ARTNodeManager.m in Sources */,
0CF68B051AF0549300FF9E5C /* ARTGroup.m in Sources */,
0CF68B131AF0549300FF9E5C /* ARTRenderableManager.m in Sources */,
0CF68B091AF0549300FF9E5C /* ARTSurfaceView.m in Sources */,
0CF68B0E1AF0549300FF9E5C /* ARTRadialGradient.m in Sources */,
0CF68B151AF0549300FF9E5C /* ARTSurfaceViewManager.m in Sources */,
0CF68B081AF0549300FF9E5C /* ARTShape.m in Sources */,
0CF68B071AF0549300FF9E5C /* ARTRenderable.m in Sources */,
0CF68B101AF0549300FF9E5C /* RCTConvert+ART.m in Sources */,
0CF68B061AF0549300FF9E5C /* ARTNode.m in Sources */,
0CF68B0F1AF0549300FF9E5C /* ARTSolidColor.m in Sources */,
0CF68B0C1AF0549300FF9E5C /* ARTLinearGradient.m in Sources */,
0CF68B0B1AF0549300FF9E5C /* ARTBrush.m in Sources */,
0CF68B141AF0549300FF9E5C /* ARTShapeManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
0CF68AD31AF0540F00FF9E5C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../React/**",
);
IPHONEOS_DEPLOYMENT_TARGET = 8.2;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
0CF68AD41AF0540F00FF9E5C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../React/**",
);
IPHONEOS_DEPLOYMENT_TARGET = 8.2;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
0CF68AD61AF0540F00FF9E5C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Debug;
};
0CF68AD71AF0540F00FF9E5C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
0CF68ABC1AF0540F00FF9E5C /* Build configuration list for PBXProject "ART" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0CF68AD31AF0540F00FF9E5C /* Debug */,
0CF68AD41AF0540F00FF9E5C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
0CF68AD51AF0540F00FF9E5C /* Build configuration list for PBXNativeTarget "ART" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0CF68AD61AF0540F00FF9E5C /* Debug */,
0CF68AD71AF0540F00FF9E5C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 0CF68AB91AF0540F00FF9E5C /* Project object */;
}

View File

@ -0,0 +1,20 @@
/**
* 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.
*/
// A little helper to make sure we have the right memory allocation ready for use.
// We assume that we will only this in one place so no reference counting is necessary.
// Needs to be freed when dealloced.
// This is fragile since this relies on these values not getting reused. Consider
// wrapping these in an Obj-C class or some ARC hackery to get refcounting.
typedef struct {
size_t count;
CGFloat *array;
} ARTCGFloatArray;

View File

@ -0,0 +1,18 @@
/**
* 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 <Foundation/Foundation.h>
@protocol ARTContainer <NSObject>
// This is used as a hook for child to mark it's parent as dirty.
// This bubbles up to the root which gets marked as dirty.
- (void)invalidate;
@end

17
Libraries/ART/ARTGroup.h Normal file
View File

@ -0,0 +1,17 @@
/**
* 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 <Foundation/Foundation.h>
#import "ARTContainer.h"
#import "ARTNode.h"
@interface ARTGroup : ARTNode <ARTContainer>
@end

23
Libraries/ART/ARTGroup.m Normal file
View File

@ -0,0 +1,23 @@
/**
* 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 "ARTGroup.h"
@implementation ARTGroup
- (void)renderLayerTo:(CGContextRef)context
{
// TO-DO: Clipping rectangle
for (ARTNode *node in self.subviews) {
[node renderTo:context];
}
}
@end

33
Libraries/ART/ARTNode.h Normal file
View File

@ -0,0 +1,33 @@
/**
* 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 <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
/**
* ART nodes are implemented as empty UIViews but this is just an implementation detail to fit
* into the existing view management. They should also be shadow views and painted on a background
* thread.
*/
@interface ARTNode : UIView
@property (nonatomic, assign) CGFloat opacity;
- (void)invalidate;
- (void)renderTo:(CGContextRef)context;
/**
* renderTo will take opacity into account and draw renderLayerTo off-screen if there is opacity
* specified, then composite that onto the context. renderLayerTo always draws at opacity=1.
* @abstract
*/
- (void)renderLayerTo:(CGContextRef)context;
@end

76
Libraries/ART/ARTNode.m Normal file
View File

@ -0,0 +1,76 @@
/**
* 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 "ARTNode.h"
#import "ARTContainer.h"
@implementation ARTNode
- (void)insertSubview:(UIView *)subview atIndex:(NSInteger)index
{
[self invalidate];
[super insertSubview:subview atIndex:index];
}
- (void)removeFromSuperview
{
[self invalidate];
[super removeFromSuperview];
}
- (void)setOpacity:(CGFloat)opacity
{
[self invalidate];
_opacity = opacity;
}
- (void)setTransform:(CGAffineTransform)transform
{
[self invalidate];
super.transform = transform;
}
- (void)invalidate
{
id<ARTContainer> container = (id<ARTContainer>)self.superview;
[container invalidate];
}
- (void)renderTo:(CGContextRef)context
{
if (self.opacity <= 0) {
// Nothing to paint
return;
}
if (self.opacity >= 1) {
// Just paint at full opacity
CGContextSaveGState(context);
CGContextConcatCTM(context, self.transform);
CGContextSetAlpha(context, 1);
[self renderLayerTo:context];
CGContextRestoreGState(context);
return;
}
// This needs to be painted on a layer before being composited.
CGContextSaveGState(context);
CGContextConcatCTM(context, self.transform);
CGContextSetAlpha(context, self.opacity);
CGContextBeginTransparencyLayer(context, NULL);
[self renderLayerTo:context];
CGContextEndTransparencyLayer(context);
CGContextRestoreGState(context);
}
- (void)renderLayerTo:(CGContextRef)context
{
// abstract
}
@end

View File

@ -0,0 +1,25 @@
/**
* 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 <Foundation/Foundation.h>
#import "ARTBrush.h"
#import "ARTCGFloatArray.h"
#import "ARTNode.h"
@interface ARTRenderable : ARTNode
@property (nonatomic, strong) ARTBrush *fill;
@property (nonatomic, assign) CGColorRef stroke;
@property (nonatomic, assign) CGFloat strokeWidth;
@property (nonatomic, assign) CGLineCap strokeCap;
@property (nonatomic, assign) CGLineJoin strokeJoin;
@property (nonatomic, assign) ARTCGFloatArray strokeDash;
@end

View File

@ -0,0 +1,89 @@
/**
* 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 "ARTRenderable.h"
@implementation ARTRenderable
- (void)setFill:(ARTBrush *)fill
{
[self invalidate];
_fill = fill;
}
- (void)setStroke:(CGColorRef)stroke
{
if (stroke == _stroke) {
return;
}
[self invalidate];
CGColorRelease(_stroke);
_stroke = CGColorRetain(stroke);
}
- (void)setStrokeWidth:(CGFloat)strokeWidth
{
[self invalidate];
_strokeWidth = strokeWidth;
}
- (void)setStrokeCap:(CGLineCap)strokeCap
{
[self invalidate];
_strokeCap = strokeCap;
}
- (void)setStrokeJoin:(CGLineJoin)strokeJoin
{
[self invalidate];
_strokeJoin = strokeJoin;
}
- (void)setStrokeDash:(ARTCGFloatArray)strokeDash
{
if (strokeDash.array == _strokeDash.array) {
return;
}
if (_strokeDash.array) {
free(_strokeDash.array);
}
[self invalidate];
_strokeDash = strokeDash;
}
- (void)dealloc
{
CGColorRelease(_stroke);
if (_strokeDash.array) {
free(_strokeDash.array);
}
}
- (void)renderTo:(CGContextRef)context
{
if (self.opacity <= 0 || self.opacity >= 1 || (self.fill && self.stroke)) {
// If we have both fill and stroke, we will need to paint this using normal compositing
[super renderTo: context];
return;
}
// This is a terminal with only one painting. Therefore we don't need to paint this
// off-screen. We can just composite it straight onto the buffer.
CGContextSaveGState(context);
CGContextConcatCTM(context, self.transform);
CGContextSetAlpha(context, self.opacity);
[self renderLayerTo:context];
CGContextRestoreGState(context);
}
- (void)renderLayerTo:(CGContextRef)context
{
// abstract
}
@end

View File

@ -0,0 +1,77 @@
/**
* 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 ARTSerializablePath
*/
"use strict";
// TODO: Move this into an ART mode called "serialized" or something
var Class = require('art/core/class.js');
var Path = require('art/core/path.js');
var MOVE_TO = 0;
var CLOSE = 1;
var LINE_TO = 2;
var CURVE_TO = 3;
var ARC = 4;
var SerializablePath = Class(Path, {
initialize: function(path) {
this.reset();
if (path instanceof SerializablePath) {
this.path = path.path.slice(0);
} else if (path) {
if (path.applyToPath) {
path.applyToPath(this);
} else {
this.push(path);
}
}
},
onReset: function() {
this.path = [];
},
onMove: function(sx, sy, x, y) {
this.path.push(MOVE_TO, x, y);
},
onLine: function(sx, sy, x, y) {
this.path.push(LINE_TO, x, y);
},
onBezierCurve: function(sx, sy, p1x, p1y, p2x, p2y, x, y) {
this.path.push(CURVE_TO, p1x, p1y, p2x, p2y, x, y);
},
_arcToBezier: Path.prototype.onArc,
onArc: function(sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation) {
if (rx !== ry || rotation) {
return this._arcToBezier(
sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation
);
}
this.path.push(ARC, cx, cy, rx, sa, ea, ccw ? 0 : 1);
},
onClose: function() {
this.path.push(CLOSE);
},
toJSON: function() {
return this.path;
}
});
module.exports = SerializablePath;

18
Libraries/ART/ARTShape.h Normal file
View File

@ -0,0 +1,18 @@
/**
* 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 <Foundation/Foundation.h>
#import "ARTRenderable.h"
@interface ARTShape : ARTRenderable
@property (nonatomic, assign) CGPathRef d;
@end

68
Libraries/ART/ARTShape.m Normal file
View File

@ -0,0 +1,68 @@
/**
* 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 "ARTShape.h"
@implementation ARTShape
- (void)setD:(CGPathRef)d
{
if (d == _d) {
return;
}
[self invalidate];
CGPathRelease(_d);
_d = CGPathRetain(d);
}
- (void)dealloc
{
CGPathRelease(_d);
}
- (void)renderLayerTo:(CGContextRef)context
{
if ((!self.fill && !self.stroke) || !self.d) {
return;
}
CGPathDrawingMode mode = kCGPathStroke;
if (self.fill) {
if ([self.fill applyFillColor:context]) {
mode = kCGPathFill;
} else {
CGContextSaveGState(context);
CGContextAddPath(context, self.d);
CGContextClip(context);
[self.fill paint:context];
CGContextRestoreGState(context);
if (!self.stroke) {
return;
}
}
}
if (self.stroke) {
CGContextSetStrokeColorWithColor(context, self.stroke);
CGContextSetLineWidth(context, self.strokeWidth);
CGContextSetLineCap(context, self.strokeCap);
CGContextSetLineJoin(context, self.strokeJoin);
ARTCGFloatArray dash = self.strokeDash;
if (dash.count) {
CGContextSetLineDash(context, 0, dash.array, dash.count);
}
if (mode == kCGPathFill) {
mode = kCGPathFillStroke;
}
}
CGContextAddPath(context, self.d);
CGContextDrawPath(context, mode);
}
@end

View File

@ -0,0 +1,16 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import "ARTContainer.h"
@interface ARTSurfaceView : UIView <ARTContainer>
@end

View File

@ -0,0 +1,30 @@
/**
* 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 "ARTSurfaceView.h"
#import "ARTNode.h"
#import "RCTLog.h"
@implementation ARTSurfaceView
- (void)invalidate
{
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
for (ARTNode *node in self.subviews) {
[node renderTo:context];
}
}
@end

20
Libraries/ART/ARTText.h Normal file
View File

@ -0,0 +1,20 @@
/**
* 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 <Foundation/Foundation.h>
#import "ARTRenderable.h"
#import "ARTTextFrame.h"
@interface ARTText : ARTRenderable
@property (nonatomic, assign) CTTextAlignment alignment;
@property (nonatomic, assign) ARTTextFrame textFrame;
@end

127
Libraries/ART/ARTText.m Normal file
View File

@ -0,0 +1,127 @@
/**
* 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 "ARTText.h"
#import <CoreText/CoreText.h>
@implementation ARTText
- (void)setAlignment:(CTTextAlignment)alignment
{
[self invalidate];
_alignment = alignment;
}
- (void)setTextFrame:(ARTTextFrame)frame
{
if (frame.lines != _textFrame.lines && _textFrame.count) {
// We must release each line before overriding the old one
for (int i = 0; i < _textFrame.count; i++) {
CFRelease(_textFrame.lines[0]);
}
free(_textFrame.lines);
free(_textFrame.widths);
}
[self invalidate];
_textFrame = frame;
}
- (void)dealloc
{
if (_textFrame.count) {
// We must release each line before freeing up this struct
for (int i = 0; i < _textFrame.count; i++) {
CFRelease(_textFrame.lines[0]);
}
free(_textFrame.lines);
free(_textFrame.widths);
}
}
- (void)renderLayerTo:(CGContextRef)context
{
ARTTextFrame frame = self.textFrame;
if ((!self.fill && !self.stroke) || !frame.count) {
return;
}
// to-do: draw along a path
CGTextDrawingMode mode = kCGTextStroke;
if (self.fill) {
if ([self.fill applyFillColor:context]) {
mode = kCGTextFill;
} else {
for (int i = 0; i < frame.count; i++) {
CGContextSaveGState(context);
// Inverse the coordinate space since CoreText assumes a bottom-up coordinate space
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetTextDrawingMode(context, kCGTextClip);
[self renderLineTo:context atIndex:i];
// Inverse the coordinate space back to the original before filling
CGContextScaleCTM(context, 1.0, -1.0);
[self.fill paint:context];
// Restore the state so that the next line can be clipped separately
CGContextRestoreGState(context);
}
if (!self.stroke) {
return;
}
}
}
if (self.stroke) {
CGContextSetStrokeColorWithColor(context, self.stroke);
CGContextSetLineWidth(context, self.strokeWidth);
CGContextSetLineCap(context, self.strokeCap);
CGContextSetLineJoin(context, self.strokeJoin);
ARTCGFloatArray dash = self.strokeDash;
if (dash.count) {
CGContextSetLineDash(context, 0, dash.array, dash.count);
}
if (mode == kCGTextFill) {
mode = kCGTextFillStroke;
}
}
CGContextSetTextDrawingMode(context, mode);
// Inverse the coordinate space since CoreText assumes a bottom-up coordinate space
CGContextScaleCTM(context, 1.0, -1.0);
for (int i = 0; i < frame.count; i++) {
[self renderLineTo:context atIndex:i];
}
}
- (void)renderLineTo:(CGContextRef)context atIndex:(int)index
{
ARTTextFrame frame = self.textFrame;
CGFloat shift;
switch (self.alignment) {
case kCTTextAlignmentRight:
shift = frame.widths[index];
break;
case kCTTextAlignmentCenter:
shift = (frame.widths[index] / 2);
break;
default:
shift = 0;
break;
}
// We should consider snapping this shift to device pixels to improve rendering quality
// when a line has subpixel width.
CGContextSetTextPosition(context, -shift, -frame.baseLine - frame.lineHeight * index);
CTLineRef line = frame.lines[index];
CTLineDraw(line, context);
}
@end

View File

@ -0,0 +1,25 @@
/**
* 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 <CoreText/CoreText.h>
// A little helper to make sure we have a set of lines including width ready for use.
// We assume that we will only this in one place so no reference counting is necessary.
// Needs to be freed when dealloced.
// This is fragile since this relies on these values not getting reused. Consider
// wrapping these in an Obj-C class or some ARC hackery to get refcounting.
typedef struct {
size_t count;
CGFloat baseLine; // Distance from the origin to the base line of the first line
CGFloat lineHeight; // Distance between lines
CTLineRef *lines;
CGFloat *widths; // Width of each line
} ARTTextFrame;

View File

@ -0,0 +1,35 @@
/**
* 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 <CoreGraphics/CoreGraphics.h>
#import <Foundation/Foundation.h>
@interface ARTBrush : NSObject
/* @abstract */
- (instancetype)initWithArray:(NSArray *)data NS_DESIGNATED_INITIALIZER;
/**
* For certain brushes we can fast path a combined fill and stroke.
* For those brushes we override applyFillColor which sets the fill
* color to be used by those batch paints. Those return YES.
* We can't batch gradient painting in CoreGraphics, so those will
* return NO and paint gets called instead.
* @abstract
*/
- (BOOL)applyFillColor:(CGContextRef)context;
/**
* paint fills the context with a brush. The context is assumed to
* be clipped.
* @abstract
*/
- (void)paint:(CGContextRef)context;
@end

View File

@ -0,0 +1,29 @@
/**
* 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 "ARTBrush.h"
@implementation ARTBrush
- (instancetype)initWithArray:(NSArray *)data
{
return [super init];
}
- (BOOL)applyFillColor:(CGContextRef)context
{
return NO;
}
- (void)paint:(CGContextRef)context
{
// abstract
}
@end

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 "ARTBrush.h"
@interface ARTLinearGradient : ARTBrush
@end

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.
*/
#import "ARTLinearGradient.h"
#import "RCTConvert+ART.h"
#import "RCTLog.h"
@implementation ARTLinearGradient
{
CGGradientRef _gradient;
CGPoint _startPoint;
CGPoint _endPoint;
}
- (instancetype)initWithArray:(NSArray *)array
{
if ((self = [super initWithArray:array])) {
if (array.count < 5) {
RCTLogError(@"-[%@ %@] expects 5 elements, received %@",
self.class, NSStringFromSelector(_cmd), array);
return nil;
}
_startPoint = [RCTConvert CGPoint:array offset:1];
_endPoint = [RCTConvert CGPoint:array offset:3];
_gradient = CGGradientRetain([RCTConvert CGGradient:array offset:5]);
}
return self;
}
- (void)dealloc
{
CGGradientRelease(_gradient);
}
- (void)paint:(CGContextRef)context
{
CGGradientDrawingOptions extendOptions =
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation;
CGContextDrawLinearGradient(context, _gradient, _startPoint, _endPoint, extendOptions);
}
@end

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 "ARTBrush.h"
@interface ARTPattern : ARTBrush
@end

View File

@ -0,0 +1,50 @@
/**
* 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 "ARTPattern.h"
#import "RCTConvert+ART.h"
#import "RCTLog.h"
@implementation ARTPattern
{
CGImageRef _image;
CGRect _rect;
}
- (instancetype)initWithArray:(NSArray *)array
{
if ((self = [super initWithArray:array])) {
if (array.count < 6) {
RCTLogError(@"-[%@ %@] expects 6 elements, received %@",
self.class, NSStringFromSelector(_cmd), array);
return nil;
}
_image = CGImageRetain([RCTConvert CGImage:array[1]]);
_rect = [RCTConvert CGRect:array offset:2];
}
return self;
}
- (void)dealloc
{
CGImageRelease(_image);
}
// Note: This could use applyFillColor with a pattern. This could be more efficient but
// to do that, we need to calculate our own user space CTM.
- (void)paint:(CGContextRef)context
{
CGContextDrawTiledImage(context, _rect, _image);
}
@end

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 "ARTBrush.h"
@interface ARTRadialGradient : ARTBrush
@end

View File

@ -0,0 +1,56 @@
/**
* 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 "ARTRadialGradient.h"
#import "RCTConvert+ART.h"
#import "RCTLog.h"
@implementation ARTRadialGradient
{
CGGradientRef _gradient;
CGPoint _focusPoint;
CGPoint _centerPoint;
CGFloat _radius;
CGFloat _radiusRatio;
}
- (instancetype)initWithArray:(NSArray *)array
{
if ((self = [super initWithArray:array])) {
if (array.count < 7) {
RCTLogError(@"-[%@ %@] expects 7 elements, received %@",
self.class, NSStringFromSelector(_cmd), array);
return nil;
}
_radius = [RCTConvert CGFloat:array[3]];
_radiusRatio = [RCTConvert CGFloat:array[4]] / _radius;
_focusPoint.x = [RCTConvert CGFloat:array[1]];
_focusPoint.y = [RCTConvert CGFloat:array[2]] / _radiusRatio;
_centerPoint.x = [RCTConvert CGFloat:array[5]];
_centerPoint.y = [RCTConvert CGFloat:array[6]] / _radiusRatio;
_gradient = CGGradientRetain([RCTConvert CGGradient:array offset:7]);
}
return self;
}
- (void)dealloc
{
CGGradientRelease(_gradient);
}
- (void)paint:(CGContextRef)context
{
CGAffineTransform transform = CGAffineTransformMakeScale(1, _radiusRatio);
CGContextConcatCTM(context, transform);
CGGradientDrawingOptions extendOptions = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation;
CGContextDrawRadialGradient(context, _gradient, _focusPoint, 0, _centerPoint, _radius, extendOptions);
}
@end

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 "ARTBrush.h"
@interface ARTSolidColor : ARTBrush
@end

View File

@ -0,0 +1,39 @@
/**
* 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 "ARTSolidColor.h"
#import "RCTConvert+ART.h"
#import "RCTLog.h"
@implementation ARTSolidColor
{
CGColorRef _color;
}
- (instancetype)initWithArray:(NSArray *)array
{
if ((self = [super initWithArray:array])) {
_color = CGColorRetain([RCTConvert CGColor:array offset:1]);
}
return self;
}
- (void)dealloc
{
CGColorRelease(_color);
}
- (BOOL)applyFillColor:(CGContextRef)context
{
CGContextSetFillColorWithColor(context, _color);
return YES;
}
@end

View File

@ -0,0 +1,31 @@
/**
* 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 <QuartzCore/QuartzCore.h>
#import "ARTBrush.h"
#import "ARTCGFloatArray.h"
#import "ARTTextFrame.h"
#import "RCTConvert.h"
@interface RCTConvert (ART)
+ (CGPathRef)CGPath:(id)json;
+ (CTFontRef)CTFont:(id)json;
+ (CTTextAlignment)CTTextAlignment:(id)json;
+ (ARTTextFrame)ARTTextFrame:(id)json;
+ (ARTCGFloatArray)ARTCGFloatArray:(id)json;
+ (ARTBrush *)ARTBrush:(id)json;
+ (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset;
+ (CGRect)CGRect:(id)json offset:(NSUInteger)offset;
+ (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset;
+ (CGGradientRef)CGGradient:(id)json offset:(NSUInteger)offset;
@end

View File

@ -0,0 +1,234 @@
/**
* 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 "RCTConvert+ART.h"
#import "ARTLinearGradient.h"
#import "ARTPattern.h"
#import "ARTRadialGradient.h"
#import "ARTSolidColor.h"
#import "RCTLog.h"
@implementation RCTConvert (ART)
+ (CGPathRef)CGPath:(id)json
{
NSArray *arr = [self NSNumberArray:json];
NSUInteger count = [arr count];
#define NEXT_VALUE [self double:arr[i++]]
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 0, 0);
@try {
NSUInteger i = 0;
while (i < count) {
NSUInteger type = [arr[i++] unsignedIntegerValue];
switch (type) {
case 0:
CGPathMoveToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE);
break;
case 1:
CGPathCloseSubpath(path);
break;
case 2:
CGPathAddLineToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE);
break;
case 3:
CGPathAddCurveToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE);
break;
case 4:
CGPathAddArc(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE == 0);
break;
default:
RCTLogError(@"Invalid CGPath type %zd at element %zd of %@", type, i, arr);
CGPathRelease(path);
return NULL;
}
}
}
@catch (NSException *exception) {
RCTLogError(@"Invalid CGPath format: %@", arr);
CGPathRelease(path);
return NULL;
}
return (CGPathRef)CFAutorelease(path);
}
+ (CTFontRef)CTFont:(id)json
{
NSDictionary *dict = [self NSDictionary:json];
if (!dict) {
return nil;
}
CTFontDescriptorRef fontDescriptor = CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)dict);
CTFontRef font = CTFontCreateWithFontDescriptor(fontDescriptor, 0.0, NULL);
CFRelease(fontDescriptor);
return (CTFontRef)CFAutorelease(font);
}
RCT_ENUM_CONVERTER(CTTextAlignment, (@{
@"auto": @(kCTTextAlignmentNatural),
@"left": @(kCTTextAlignmentLeft),
@"center": @(kCTTextAlignmentCenter),
@"right": @(kCTTextAlignmentRight),
@"justify": @(kCTTextAlignmentJustified),
}), kCTTextAlignmentNatural, integerValue)
// This takes a tuple of text lines and a font to generate a CTLine for each text line.
// This prepares everything for rendering a frame of text in ARTText.
+ (ARTTextFrame)ARTTextFrame:(id)json
{
NSDictionary *dict = [self NSDictionary:json];
ARTTextFrame frame;
frame.count = 0;
NSArray *lines = [self NSArray:dict[@"lines"]];
NSUInteger lineCount = [lines count];
if (lineCount == 0) {
return frame;
}
CTFontRef font = [self CTFont:dict[@"font"]];
if (!font) {
return frame;
}
// Create a dictionary for this font
CFDictionaryRef attributes = (__bridge CFDictionaryRef)@{
(NSString *)kCTFontAttributeName: (__bridge id)font,
(NSString *)kCTForegroundColorFromContextAttributeName: @YES
};
// Set up text frame with font metrics
CGFloat size = CTFontGetSize(font);
frame.count = lineCount;
frame.baseLine = size; // estimate base line
frame.lineHeight = size * 1.1; // Base on ART canvas line height estimate
frame.lines = malloc(sizeof(CTLineRef) * lineCount);
frame.widths = malloc(sizeof(CGFloat) * lineCount);
[lines enumerateObjectsUsingBlock:^(NSString *text, NSUInteger i, BOOL *stop) {
CFStringRef string = (__bridge CFStringRef)text;
CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
CTLineRef line = CTLineCreateWithAttributedString(attrString);
CFRelease(attrString);
frame.lines[i] = line;
frame.widths[i] = CTLineGetTypographicBounds(line, NULL, NULL, NULL);
}];
return frame;
}
+ (ARTCGFloatArray)ARTCGFloatArray:(id)json
{
NSArray *arr = [self NSNumberArray:json];
NSUInteger count = arr.count;
ARTCGFloatArray array;
array.count = count;
array.array = NULL;
if (count) {
// Ideally, these arrays should already use the same memory layout.
// In that case we shouldn't need this new malloc.
array.array = malloc(sizeof(CGFloat) * count);
for (NSUInteger i = 0; i < count; i++) {
array.array[i] = [arr[i] doubleValue];
}
}
return array;
}
+ (ARTBrush *)ARTBrush:(id)json
{
NSArray *arr = [self NSArray:json];
NSUInteger type = [self NSUInteger:arr[0]];
switch (type) {
case 0: // solid color
// These are probably expensive allocations since it's often the same value.
// We should memoize colors but look ups may be just as expensive.
return [[ARTSolidColor alloc] initWithArray:arr];
case 1: // linear gradient
return [[ARTLinearGradient alloc] initWithArray:arr];
case 2: // radial gradient
return [[ARTRadialGradient alloc] initWithArray:arr];
case 3: // pattern
return [[ARTPattern alloc] initWithArray:arr];
default:
RCTLogError(@"Unknown brush type: %zd", type);
return nil;
}
}
+ (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset
{
NSArray *arr = [self NSArray:json];
if (arr.count < offset + 2) {
RCTLogError(@"Too few elements in array (expected at least %zd): %@", 2 + offset, arr);
return CGPointZero;
}
return (CGPoint){
[self CGFloat:arr[offset]],
[self CGFloat:arr[offset + 1]],
};
}
+ (CGRect)CGRect:(id)json offset:(NSUInteger)offset
{
NSArray *arr = [self NSArray:json];
if (arr.count < offset + 4) {
RCTLogError(@"Too few elements in array (expected at least %zd): %@", 4 + offset, arr);
return CGRectZero;
}
return (CGRect){
{[self CGFloat:arr[offset]], [self CGFloat:arr[offset + 1]]},
{[self CGFloat:arr[offset + 2]], [self CGFloat:arr[offset + 3]]},
};
}
+ (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset
{
NSArray *arr = [self NSArray:json];
if (arr.count < offset + 4) {
RCTLogError(@"Too few elements in array (expected at least %zd): %@", 4 + offset, arr);
return NULL;
}
return [self CGColor:[arr subarrayWithRange:(NSRange){offset, 4}]];
}
+ (CGGradientRef)CGGradient:(id)json offset:(NSUInteger)offset
{
NSArray *arr = [self NSArray:json];
if (arr.count < offset) {
RCTLogError(@"Too few elements in array (expected at least %zd): %@", offset, arr);
return NULL;
}
arr = [arr subarrayWithRange:(NSRange){offset, arr.count - offset}];
ARTCGFloatArray colorsAndOffsets = [self ARTCGFloatArray:arr];
size_t stops = colorsAndOffsets.count / 5;
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(
rgb,
colorsAndOffsets.array,
colorsAndOffsets.array + stops * 4,
stops
);
CGColorSpaceRelease(rgb);
free(colorsAndOffsets.array);
return (CGGradientRef)CFAutorelease(gradient);
}
@end

View File

@ -0,0 +1,611 @@
/**
* 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 ReactIOSART
*/
"use strict";
var Color = require('art/core/color');
var Path = require('ARTSerializablePath');
var Transform = require('art/core/transform');
var React = require('React');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
var merge = require('merge');
// Diff Helpers
function arrayDiffer(a, b) {
if (a == null) {
return true;
}
if (a.length !== b.length) {
return true;
}
for (var i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return true;
}
}
return false;
}
function fontAndLinesDiffer(a, b) {
if (a === b) {
return false;
}
if (a.font !== b.font) {
if (a.font === null) {
return true;
}
if (b.font === null) {
return true;
}
var aTraits = a.font.NSCTFontTraitsAttribute;
var bTraits = b.font.NSCTFontTraitsAttribute;
if (
a.font.fontFamily !== b.font.fontFamily ||
a.font.fontSize !== b.font.fontSize ||
a.font.fontWeight !== b.font.fontWeight ||
a.font.fontStyle !== b.font.fontStyle ||
// TODO(6364240): remove iOS-specific attrs
a.font.NSFontFamilyAttribute !== b.font.NSFontFamilyAttribute ||
a.font.NSFontSizeAttribute !== b.font.NSFontSizeAttribute ||
aTraits.NSCTFontSymbolicTrait !== bTraits.NSCTFontSymbolicTrait
) {
return true;
}
}
return arrayDiffer(a.lines, b.lines);
}
// Native Attributes
var SurfaceViewAttributes = merge(ReactIOSViewAttributes.UIView, {
// This should contain pixel information such as width, height and
// resolution to know what kind of buffer needs to be allocated.
// Currently we rely on UIViews and style to figure that out.
});
var NodeAttributes = {
transform: { diff: arrayDiffer },
opacity: true,
};
var GroupAttributes = merge(NodeAttributes, {
clipping: { diff: arrayDiffer }
});
var RenderableAttributes = merge(NodeAttributes, {
fill: { diff: arrayDiffer },
stroke: { diff: arrayDiffer },
strokeWidth: true,
strokeCap: true,
strokeJoin: true,
strokeDash: { diff: arrayDiffer },
});
var ShapeAttributes = merge(RenderableAttributes, {
d: { diff: arrayDiffer },
});
var TextAttributes = merge(RenderableAttributes, {
alignment: true,
frame: { diff: fontAndLinesDiffer },
path: { diff: arrayDiffer }
});
// Native Components
var NativeSurfaceView = createReactIOSNativeComponentClass({
validAttributes: SurfaceViewAttributes,
uiViewClassName: 'ARTSurfaceView',
});
var NativeGroup = createReactIOSNativeComponentClass({
validAttributes: GroupAttributes,
uiViewClassName: 'ARTGroup',
});
var NativeShape = createReactIOSNativeComponentClass({
validAttributes: ShapeAttributes,
uiViewClassName: 'ARTShape',
});
var NativeText = createReactIOSNativeComponentClass({
validAttributes: TextAttributes,
uiViewClassName: 'ARTText',
});
// Utilities
function childrenAsString(children) {
if (!children) {
return '';
}
if (typeof children === 'string') {
return children;
}
if (children.length) {
return children.join('\n');
}
return '';
}
// Surface - Root node of all ART
var Surface = React.createClass({
render: function() {
var props = this.props;
var w = extractNumber(props.width, 0);
var h = extractNumber(props.height, 0);
return (
<NativeSurfaceView style={[props.style, { width: w, height: h }]}>
{this.props.children}
</NativeSurfaceView>
);
}
});
// Node Props
// TODO: The desktop version of ART has title and cursor. We should have
// accessibility support here too even though hovering doesn't work.
function extractNumber(value, defaultValue) {
if (value == null) {
return defaultValue;
}
return +value;
}
var pooledTransform = new Transform();
function extractTransform(props) {
var scaleX = props.scaleX != null ? props.scaleX :
props.scale != null ? props.scale : 1;
var scaleY = props.scaleY != null ? props.scaleY :
props.scale != null ? props.scale : 1;
pooledTransform
.transformTo(1, 0, 0, 1, 0, 0)
.move(props.x || 0, props.y || 0)
.rotate(props.rotation || 0, props.originX, props.originY)
.scale(scaleX, scaleY, props.originX, props.originY);
if (props.transform != null) {
pooledTransform.transform(props.transform);
}
return [
pooledTransform.xx, pooledTransform.yx,
pooledTransform.xy, pooledTransform.yy,
pooledTransform.x, pooledTransform.y,
];
}
function extractOpacity(props) {
// TODO: visible === false should also have no hit detection
if (props.visible === false) {
return 0;
}
if (props.opacity == null) {
return 1;
}
return +props.opacity;
}
// Groups
// Note: ART has a notion of width and height on Group but AFAIK it's a noop in
// ReactART.
var Group = React.createClass({
render: function() {
var props = this.props;
return (
<NativeGroup
opacity={extractOpacity(props)}
transform={extractTransform(props)}>
{this.props.children}
</NativeGroup>
);
}
});
var ClippingRectangle = React.createClass({
render: function() {
var props = this.props;
var x = extractNumber(props.x, 0);
var y = extractNumber(props.y, 0);
var w = extractNumber(props.width, 0);
var h = extractNumber(props.height, 0);
var clipping = new Path()
.moveTo(x, y)
.line(w, 0)
.line(0, h)
.line(w, 0)
.close()
.toJSON();
// The current clipping API requires x and y to be ignored in the transform
var propsExcludingXAndY = merge(props);
delete propsExcludingXAndY.x;
delete propsExcludingXAndY.y;
return (
<NativeGroup
clipping={clipping}
opacity={extractOpacity(props)}
transform={extractTransform(propsExcludingXAndY)}>
{this.props.children}
</NativeGroup>
);
}
});
// Renderables
var SOLID_COLOR = 0;
var LINEAR_GRADIENT = 1;
var RADIAL_GRADIENT = 2;
var PATTERN = 3;
function insertColorIntoArray(color, targetArray, atIndex) {
var c = new Color(color);
targetArray[atIndex + 0] = c.red / 255;
targetArray[atIndex + 1] = c.green / 255;
targetArray[atIndex + 2] = c.blue / 255;
targetArray[atIndex + 3] = c.alpha;
}
function insertColorsIntoArray(stops, targetArray, atIndex) {
var i = 0;
if ('length' in stops) {
while (i < stops.length) {
insertColorIntoArray(stops[i], targetArray, atIndex + i * 4);
i++;
}
} else {
for (var offset in stops) {
insertColorIntoArray(stops[offset], targetArray, atIndex + i * 4);
i++;
}
}
return atIndex + i * 4;
}
function insertOffsetsIntoArray(stops, targetArray, atIndex, multi, reverse) {
var offsetNumber;
var i = 0;
if ('length' in stops) {
while (i < stops.length) {
offsetNumber = i / (stops.length - 1) * multi;
targetArray[atIndex + i] = reverse ? 1 - offsetNumber : offsetNumber;
i++;
}
} else {
for (var offsetString in stops) {
offsetNumber = (+offsetString) * multi;
targetArray[atIndex + i] = reverse ? 1 - offsetNumber : offsetNumber;
i++;
}
}
return atIndex + i;
}
function insertColorStopsIntoArray(stops, targetArray, atIndex) {
var lastIndex = insertColorsIntoArray(stops, targetArray, atIndex);
insertOffsetsIntoArray(stops, targetArray, lastIndex, 1, false);
}
function insertDoubleColorStopsIntoArray(stops, targetArray, atIndex) {
var lastIndex = insertColorsIntoArray(stops, targetArray, atIndex);
lastIndex = insertColorsIntoArray(stops, targetArray, lastIndex);
lastIndex = insertOffsetsIntoArray(stops, targetArray, lastIndex, 0.5, false);
insertOffsetsIntoArray(stops, targetArray, lastIndex, 0.5, true);
}
function applyBoundingBoxToBrushData(brushData, props) {
var type = brushData[0];
var width = +props.width;
var height = +props.height;
if (type === LINEAR_GRADIENT) {
brushData[1] *= width;
brushData[2] *= height;
brushData[3] *= width;
brushData[4] *= height;
} else if (type === RADIAL_GRADIENT) {
brushData[1] *= width;
brushData[2] *= height;
brushData[3] *= width;
brushData[4] *= height;
brushData[5] *= width;
brushData[6] *= height;
} else if (type === PATTERN) {
// todo
}
}
function extractBrush(colorOrBrush, props) {
if (colorOrBrush == null) {
return null;
}
if (colorOrBrush._brush) {
if (colorOrBrush._bb) {
// The legacy API for Gradients allow for the bounding box to be used
// as a convenience for specifying gradient positions. This should be
// deprecated. It's not properly implemented in canvas mode. ReactART
// doesn't handle update to the bounding box correctly. That's why we
// mutate this so that if it's reused, we reuse the same resolved box.
applyBoundingBoxToBrushData(colorOrBrush._brush, props);
colorOrBrush._bb = false;
}
return colorOrBrush._brush;
}
var c = new Color(colorOrBrush);
return [SOLID_COLOR, c.red / 255, c.green / 255, c.blue / 255, c.alpha];
}
function extractColor(color) {
if (color == null) {
return null;
}
var c = new Color(color);
return [c.red / 255, c.green / 255, c.blue / 255, c.alpha];
}
function extractStrokeCap(strokeCap) {
switch (strokeCap) {
case 'butt': return 0;
case 'square': return 2;
default: return 1; // round
}
}
function extractStrokeJoin(strokeJoin) {
switch (strokeJoin) {
case 'miter': return 0;
case 'bevel': return 2;
default: return 1; // round
}
}
// Shape
// Note: ART has a notion of width and height on Shape but AFAIK it's a noop in
// ReactART.
var Shape = React.createClass({
render: function() {
var props = this.props;
var path = props.d || childrenAsString(props.children);
var d = new Path(path).toJSON();
return (
<NativeShape
fill={extractBrush(props.fill, props)}
opacity={extractOpacity(props)}
stroke={extractColor(props.stroke)}
strokeCap={extractStrokeCap(props.strokeCap)}
strokeDash={props.strokeDash || null}
strokeJoin={extractStrokeJoin(props.strokeJoin)}
strokeWidth={extractNumber(props.strokeWidth, 1)}
transform={extractTransform(props)}
d={d}
/>
);
}
});
// Text
var cachedFontObjectsFromString = {};
function extractFontTraits(isBold, isItalic) {
var italic = isItalic ? 1 : 0;
var bold = isBold ? 2 : 0;
return {
NSCTFontSymbolicTrait: italic | bold
};
}
var fontFamilyPrefix = /^[\s"']*/;
var fontFamilySuffix = /[\s"']*$/;
function extractSingleFontFamily(fontFamilyString) {
// ART on the web allows for multiple font-families to be specified.
// For compatibility, we extract the first font-family, hoping
// we'll get a match.
return fontFamilyString.split(',')[0]
.replace(fontFamilyPrefix, '')
.replace(fontFamilySuffix, '');
}
function parseFontString(font) {
if (cachedFontObjectsFromString.hasOwnProperty(font)) {
return cachedFontObjectsFromString[font];
}
var regexp = /^\s*((?:(?:normal|bold|italic)\s+)*)(?:(\d+(?:\.\d+)?)[ptexm\%]*(?:\s*\/.*?)?\s+)?\s*\"?([^\"]*)/i;
var match = regexp.exec(font);
if (!match) {
return null;
}
var fontFamily = extractSingleFontFamily(match[3]);
var fontSize = +match[2] || 12;
var isBold = /bold/.exec(match[1]);
var isItalic = /italic/.exec(match[1]);
cachedFontObjectsFromString[font] = {
fontFamily: fontFamily,
fontSize: fontSize,
fontWeight: isBold ? 'bold' : 'normal',
fontStyle: isItalic ? 'italic' : 'normal',
// TODO(6364240): remove iOS-specific attrs
NSFontFamilyAttribute: fontFamily,
NSFontSizeAttribute: fontSize,
NSCTFontTraitsAttribute: extractFontTraits(isBold, isItalic)
};
return cachedFontObjectsFromString[font];
}
function extractFont(font) {
if (font == null) {
return null;
}
if (typeof font === 'string') {
return parseFontString(font);
}
var fontFamily = extractSingleFontFamily(font.fontFamily);
var fontSize = +font.fontSize || 12;
return {
// Normalize
fontFamily: fontFamily,
fontSize: fontSize,
fontWeight: font.fontWeight,
fontStyle: font.fontStyle,
// TODO(6364240): remove iOS-specific attrs
NSFontFamilyAttribute: fontFamily,
NSFontSizeAttribute: fontSize,
NSCTFontTraitsAttribute: extractFontTraits(
font.fontWeight === 'bold',
font.fontStyle === 'italic'
)
};
}
var newLine = /\n/g;
function extractFontAndLines(font, text) {
return { font: extractFont(font), lines: text.split(newLine) };
}
function extractAlignment(alignment) {
switch (alignment) {
case 'right':
return 1;
case 'center':
return 2;
default:
return 0;
}
}
var Text = React.createClass({
render: function() {
var props = this.props;
var textPath = props.path ? new Path(props.path).toJSON() : null;
var textFrame = extractFontAndLines(
props.font,
childrenAsString(props.children)
);
return (
<NativeText
fill={extractBrush(props.fill, props)}
opacity={extractOpacity(props)}
stroke={extractColor(props.stroke)}
strokeCap={extractStrokeCap(props.strokeCap)}
strokeDash={props.strokeDash || null}
strokeJoin={extractStrokeJoin(props.strokeJoin)}
strokeWidth={extractNumber(props.strokeWidth, 1)}
transform={extractTransform(props)}
alignment={extractAlignment(props.alignment)}
frame={textFrame}
path={textPath}
/>
);
}
});
// Declarative fill type objects - API design not finalized
function LinearGradient(stops, x1, y1, x2, y2) {
var type = LINEAR_GRADIENT;
if (arguments.length < 5) {
var angle = ((x1 == null) ? 270 : x1) * Math.PI / 180;
var x = Math.cos(angle);
var y = -Math.sin(angle);
var l = (Math.abs(x) + Math.abs(y)) / 2;
x *= l; y *= l;
x1 = 0.5 - x;
x2 = 0.5 + x;
y1 = 0.5 - y;
y2 = 0.5 + y;
this._bb = true;
} else {
this._bb = false;
}
var brushData = [type, +x1, +y1, +x2, +y2];
insertColorStopsIntoArray(stops, brushData, 5);
this._brush = brushData;
}
function RadialGradient(stops, fx, fy, rx, ry, cx, cy) {
if (ry == null) {
ry = rx;
}
if (cx == null) {
cx = fx;
}
if (cy == null) {
cy = fy;
}
if (fx == null) {
// As a convenience we allow the whole radial gradient to cover the
// bounding box. We should consider dropping this API.
fx = fy = rx = ry = cx = cy = 0.5;
this._bb = true;
} else {
this._bb = false;
}
// The ART API expects the radial gradient to be repeated at the edges.
// To simulate this we render the gradient twice as large and add double
// color stops. Ideally this API would become more restrictive so that this
// extra work isn't needed.
var brushData = [RADIAL_GRADIENT, +fx, +fy, +rx * 2, +ry * 2, +cx, +cy];
insertDoubleColorStopsIntoArray(stops, brushData, 7);
this._brush = brushData;
}
function Pattern(url, width, height, left, top) {
this._brush = [PATTERN, url, +left || 0, +top || 0, +width, +height];
}
var ReactART = {
LinearGradient: LinearGradient,
RadialGradient: RadialGradient,
Pattern: Pattern,
Transform: Transform,
Path: Path,
Surface: Surface,
Group: Group,
ClippingRectangle: ClippingRectangle,
Shape: Shape,
Text: Text,
};
module.exports = ReactART;

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 "ARTNodeManager.h"
@interface ARTGroupManager : ARTNodeManager
@end

View File

@ -0,0 +1,23 @@
/**
* 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 "ARTGroupManager.h"
#import "ARTGroup.h"
@implementation ARTGroupManager
RCT_EXPORT_MODULE()
- (ARTNode *)node
{
return [[ARTGroup alloc] init];
}
@end

View File

@ -0,0 +1,17 @@
/**
* 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 "ARTNode.h"
#import "RCTViewManager.h"
@interface ARTNodeManager : RCTViewManager
- (ARTNode *)node;
@end

View File

@ -0,0 +1,36 @@
/**
* 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 "ARTNodeManager.h"
#import "ARTNode.h"
@implementation ARTNodeManager
RCT_EXPORT_MODULE()
- (ARTNode *)node
{
return [[ARTNode alloc] init];
}
- (UIView *)view
{
return [self node];
}
- (RCTShadowView *)shadowView
{
return nil;
}
RCT_EXPORT_VIEW_PROPERTY(opacity, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(transform, CGAffineTransform)
@end

View File

@ -0,0 +1,17 @@
/**
* 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 "ARTNodeManager.h"
#import "ARTRenderable.h"
@interface ARTRenderableManager : ARTNodeManager
- (ARTRenderable *)node;
@end

View File

@ -0,0 +1,30 @@
/**
* 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 "ARTRenderableManager.h"
#import "RCTConvert+ART.h"
@implementation ARTRenderableManager
RCT_EXPORT_MODULE()
- (ARTRenderable *)node
{
return [[ARTRenderable alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(strokeWidth, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(strokeCap, CGLineCap)
RCT_EXPORT_VIEW_PROPERTY(strokeJoin, CGLineJoin)
RCT_EXPORT_VIEW_PROPERTY(fill, ARTBrush)
RCT_EXPORT_VIEW_PROPERTY(stroke, CGColor)
RCT_EXPORT_VIEW_PROPERTY(strokeDash, ARTCGFloatArray)
@end

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 "ARTRenderableManager.h"
@interface ARTShapeManager : ARTRenderableManager
@end

View File

@ -0,0 +1,26 @@
/**
* 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 "ARTShapeManager.h"
#import "ARTShape.h"
#import "RCTConvert+ART.h"
@implementation ARTShapeManager
RCT_EXPORT_MODULE()
- (ARTRenderable *)node
{
return [[ARTShape alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(d, CGPath)
@end

View File

@ -9,6 +9,6 @@
#import "RCTViewManager.h"
@interface RCTUIActivityIndicatorViewManager : RCTViewManager
@interface ARTSurfaceViewManager : RCTViewManager
@end

View File

@ -0,0 +1,23 @@
/**
* 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 "ARTSurfaceViewManager.h"
#import "ARTSurfaceView.h"
@implementation ARTSurfaceViewManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[ARTSurfaceView alloc] init];
}
@end

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 "ARTRenderableManager.h"
@interface ARTTextManager : ARTRenderableManager
@end

View File

@ -0,0 +1,27 @@
/**
* 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 "ARTTextManager.h"
#import "ARTText.h"
#import "RCTConvert+ART.h"
@implementation ARTTextManager
RCT_EXPORT_MODULE()
- (ARTRenderable *)node
{
return [[ARTText alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(alignment, CTTextAlignment)
RCT_REMAP_VIEW_PROPERTY(frame, textFrame, ARTTextFrame)
@end

View File

@ -5,9 +5,9 @@
var NativeModules = {
I18n: {
translationsDictionary: {
translationsDictionary: JSON.stringify({
'Good bye, {name}!|Bye message': '¡Adiós {name}!',
},
}),
},
Timing: {
createTimer: jest.genMockFunction(),
@ -29,6 +29,7 @@ var NativeModules = {
UIManager: {
customBubblingEventTypes: {},
customDirectEventTypes: {},
Dimensions: {},
},
AsyncLocalStorage: {
getItem: jest.genMockFunction(),

View File

@ -18,21 +18,16 @@ var React = require('React');
var StyleSheet = require('StyleSheet');
var View = require('View');
var keyMirror = require('keyMirror');
var requireNativeComponent = require('requireNativeComponent');
var verifyPropTypes = require('verifyPropTypes');
var SpinnerSize = keyMirror({
large: null,
small: null,
});
var GRAY = '#999999';
type DefaultProps = {
animating: boolean;
size: 'small' | 'large';
color: string;
hidesWhenStopped: boolean;
size: 'small' | 'large';
};
var ActivityIndicatorIOS = React.createClass({
@ -47,7 +42,10 @@ var ActivityIndicatorIOS = React.createClass({
* The foreground color of the spinner (default is gray).
*/
color: PropTypes.string,
/**
* Whether the indicator should hide when not animating (true by default).
*/
hidesWhenStopped: PropTypes.bool,
/**
* Size of the indicator. Small has a height of 20, large has a height of 36.
*/
@ -60,27 +58,18 @@ var ActivityIndicatorIOS = React.createClass({
getDefaultProps: function(): DefaultProps {
return {
animating: true,
size: SpinnerSize.small,
color: GRAY,
hidesWhenStopped: true,
size: 'small',
};
},
render: function() {
var style = styles.sizeSmall;
var NativeConstants = NativeModules.UIManager.UIActivityIndicatorView.Constants;
var activityIndicatorViewStyle = NativeConstants.StyleWhite;
if (this.props.size === 'large') {
style = styles.sizeLarge;
activityIndicatorViewStyle = NativeConstants.StyleWhiteLarge;
}
var {style, ...props} = this.props;
var sizeStyle = (this.props.size === 'large') ? styles.sizeLarge : styles.sizeSmall;
return (
<View
style={[styles.container, style, this.props.style]}>
<UIActivityIndicatorView
activityIndicatorViewStyle={activityIndicatorViewStyle}
animating={this.props.animating}
color={this.props.color}
/>
<View style={[styles.container, sizeStyle, style]}>
<RCTActivityIndicatorView {...props} />
</View>
);
}
@ -99,15 +88,15 @@ var styles = StyleSheet.create({
}
});
var UIActivityIndicatorView = requireNativeComponent(
'UIActivityIndicatorView',
var RCTActivityIndicatorView = requireNativeComponent(
'RCTActivityIndicatorView',
null
);
if (__DEV__) {
var nativeOnlyProps = {activityIndicatorViewStyle: true};
verifyPropTypes(
ActivityIndicatorIOS,
UIActivityIndicatorView.viewConfig,
RCTActivityIndicatorView.viewConfig,
nativeOnlyProps
);
}

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

View File

@ -0,0 +1,120 @@
/**
* 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 SegmentedControlIOS
* @flow
*/
'use strict';
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');
type DefaultProps = {
values: Array<string>;
enabled: boolean;
};
var SEGMENTED_CONTROL_REFERENCE = 'segmentedcontrol';
type Event = Object;
/**
* Use `SegmentedControlIOS` to render a UISegmentedControl iOS.
*/
var SegmentedControlIOS = React.createClass({
mixins: [NativeMethodsMixin],
propTypes: {
/**
* The labels for the control's segment buttons, in order.
*/
values: PropTypes.arrayOf(PropTypes.string),
/**
* The index in `props.values` of the segment to be pre-selected
*/
selectedIndex: PropTypes.number,
/**
* Callback that is called when the user taps a segment;
* passes the segment's value as an argument
*/
onValueChange: PropTypes.func,
/**
* Callback that is called when the user taps a segment;
* passes the event as an argument
*/
onChange: PropTypes.func,
/**
* If false the user won't be able to interact with the control.
* Default value is true.
*/
enabled: PropTypes.bool,
/**
* Accent color of the control.
*/
tintColor: PropTypes.string,
/**
* If true, then selecting a segment won't persist visually.
* The `onValueChange` callback will still work as expected.
*/
momentary: PropTypes.bool
},
getDefaultProps: function(): DefaultProps {
return {
values: [],
enabled: true
};
},
_onChange: function(event: Event) {
this.props.onChange && this.props.onChange(event);
this.props.onValueChange && this.props.onValueChange(event.nativeEvent.value);
},
render: function() {
return (
<RCTSegmentedControl
{...this.props}
ref={SEGMENTED_CONTROL_REFERENCE}
style={[styles.segmentedControl, this.props.style]}
onChange={this._onChange}
/>
);
}
});
var styles = StyleSheet.create({
segmentedControl: {
height: NativeModules.SegmentedControlManager.ComponentHeight
},
});
var RCTSegmentedControl = requireNativeComponent(
'RCTSegmentedControl',
null
);
if (__DEV__) {
verifyPropTypes(
RCTSegmentedControl,
RCTSegmentedControl.viewConfig
);
}
module.exports = SegmentedControlIOS;

View File

@ -66,6 +66,9 @@ var TabBarItemIOS = React.createClass({
* blank content, you probably forgot to add a selected one.
*/
selected: React.PropTypes.bool,
/**
* React style object.
*/
style: View.propTypes.style,
/**
* Text that appears under the icon. It is ignored when a system icon

View File

@ -38,6 +38,7 @@ var returnKeyTypeConsts = RCTUIManager.UIReturnKeyType;
var RCTTextViewAttributes = merge(ReactIOSViewAttributes.UIView, {
autoCorrect: true,
autoCapitalize: true,
clearTextOnFocus: true,
color: true,
editable: true,
fontFamily: true,
@ -48,6 +49,7 @@ var RCTTextViewAttributes = merge(ReactIOSViewAttributes.UIView, {
returnKeyType: true,
enablesReturnKeyAutomatically: true,
secureTextEntry: true,
selectTextOnFocus: true,
mostRecentEventCounter: true,
placeholder: true,
placeholderTextColor: true,
@ -445,6 +447,7 @@ var TextInput = React.createClass({
onSubmitEditing={this.props.onSubmitEditing}
onSelectionChangeShouldSetResponder={() => true}
placeholder={this.props.placeholder}
placeholderTextColor={this.props.placeholderTextColor}
text={this.state.bufferedValue}
autoCapitalize={autoCapitalize}
autoCorrect={this.props.autoCorrect}
@ -498,6 +501,8 @@ var TextInput = React.createClass({
autoCapitalize={autoCapitalize}
autoCorrect={this.props.autoCorrect}
clearButtonMode={clearButtonMode}
selectTextOnFocus={this.props.selectTextOnFocus}
clearTextOnFocus={this.props.clearTextOnFocus}
/>;
}

View File

@ -29,7 +29,7 @@ var stylePropType = StyleSheetPropType(ViewStylePropTypes);
* container that supports layout with flexbox, style, some touch handling, and
* accessibility controls, and is designed to be nested inside other views and
* to have 0 to many children of any type. `View` maps directly to the native
* view equivalent on whatever platform react is running on, whether that is a
* view equivalent on whatever platform React is running on, whether that is a
* `UIView`, `<div>`, `android.view`, etc. This example creates a `View` that
* wraps two colored boxes and custom component in a row with padding.
*

View File

@ -42,6 +42,7 @@ var WebView = React.createClass({
onNavigationStateChange: PropTypes.func,
startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
style: View.propTypes.style,
javaScriptEnabledAndroid: PropTypes.bool,
/**
* Used to locate this view in end-to-end tests.
*/
@ -90,6 +91,7 @@ var WebView = React.createClass({
key="webViewKey"
style={webViewStyles}
url={this.props.url}
javaScriptEnabledAndroid={this.props.javaScriptEnabledAndroid}
contentInset={this.props.contentInset}
automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets}
onLoadingStart={this.onLoadingStart}
@ -157,6 +159,7 @@ var WebView = React.createClass({
var RCTWebView = createReactIOSNativeComponentClass({
validAttributes: merge(ReactIOSViewAttributes.UIView, {
url: true,
javaScriptEnabledAndroid: true,
}),
uiViewClassName: 'RCTWebView',
});

View File

@ -92,6 +92,10 @@ var WebView = React.createClass({
onNavigationStateChange: PropTypes.func,
startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
style: View.propTypes.style,
/**
* Used for android only, JS is enabled by default for WebView on iOS
*/
javaScriptEnabledAndroid: PropTypes.bool,
},
getInitialState: function() {

View File

@ -267,7 +267,8 @@ var Navigator = React.createClass({
},
contextTypes: {
navigator: PropTypes.object,
// TODO (t6707746) Re-enable this when owner context switches to parent context
// navigator: PropTypes.object,
},
statics: {
@ -314,13 +315,11 @@ var Navigator = React.createClass({
// On first render, we will render every scene in the initialRouteStack
updatingRangeStart: 0,
updatingRangeLength: routeStack.length,
// Either animating or gesturing.
isAnimating: false,
jumpToIndex: routeStack.length - 1,
presentedIndex: initialRouteIndex,
isResponderOnlyToBlockTouches: false,
fromIndex: initialRouteIndex,
toIndex: initialRouteIndex,
transitionFromIndex: null,
activeGesture: null,
pendingGestureProgress: null,
transitionQueue: [],
};
},
@ -355,9 +354,24 @@ var Navigator = React.createClass({
popToTop: this.popToTop,
};
this._handlers = {};
this.springSystem = new rebound.SpringSystem();
this.spring = this.springSystem.createSpring();
this.spring.setRestSpeedThreshold(0.05);
this.spring.setCurrentValue(0).setAtRest();
this.spring.addListener({
onSpringEndStateChange: () => {
if (!this._interactionHandle) {
this._interactionHandle = this.createInteractionHandle();
}
},
onSpringUpdate: () => {
this._handleSpringUpdate();
},
onSpringAtRest: () => {
this._completeTransition();
},
});
this.panGesture = PanResponder.create({
onStartShouldSetPanResponderCapture: this._handleStartShouldSetPanResponderCapture,
onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
onPanResponderGrant: this._handlePanResponderGrant,
onPanResponderRelease: this._handlePanResponderRelease,
@ -426,20 +440,8 @@ var Navigator = React.createClass({
this._handlers[this.state.routeStack.indexOf(route)] = handler;
},
_configureSpring: function(animationConfig) {
var config = this.spring.getSpringConfig();
config.friction = animationConfig.springFriction;
config.tension = animationConfig.springTension;
},
componentDidMount: function() {
this.springSystem = new rebound.SpringSystem();
this.spring = this.springSystem.createSpring();
this.spring.setRestSpeedThreshold(0.05);
var animationConfig = this.state.sceneConfigStack[this.state.presentedIndex];
animationConfig && this._configureSpring(animationConfig);
this.spring.addListener(this);
this.onSpringUpdate();
this._handleSpringUpdate();
this._emitDidFocus(this.state.routeStack[this.state.presentedIndex]);
if (this.parentNavigator) {
this.parentNavigator.setHandler(this._handleRequest);
@ -483,98 +485,113 @@ var Navigator = React.createClass({
updatingRangeStart: 0,
updatingRangeLength: nextRouteStack.length,
presentedIndex: destIndex,
jumpToIndex: destIndex,
toIndex: destIndex,
fromIndex: destIndex,
activeGesture: null,
transitionFromIndex: null,
transitionQueue: [],
}, () => {
this.onSpringUpdate();
this._handleSpringUpdate();
});
},
_transitionTo: function(destIndex, velocity, jumpSpringTo, cb) {
if (destIndex === this.state.presentedIndex) {
return;
}
if (this.state.transitionFromIndex !== null) {
this.state.transitionQueue.push({
destIndex,
velocity,
cb,
});
return;
}
this.state.transitionFromIndex = this.state.presentedIndex;
this.state.presentedIndex = destIndex;
this.state.transitionCb = cb;
this._onAnimationStart();
if (AnimationsDebugModule) {
AnimationsDebugModule.startRecordingFps();
}
var sceneConfig = this.state.sceneConfigStack[this.state.transitionFromIndex] ||
this.state.sceneConfigStack[this.state.presentedIndex];
invariant(
sceneConfig,
'Cannot configure scene at index ' + this.state.transitionFromIndex
);
if (jumpSpringTo != null) {
this.spring.setCurrentValue(jumpSpringTo);
}
this.spring.setOvershootClampingEnabled(true);
this.spring.getSpringConfig().friction = sceneConfig.springFriction;
this.spring.getSpringConfig().tension = sceneConfig.springTension;
this.spring.setVelocity(velocity || sceneConfig.defaultTransitionVelocity);
this.spring.setEndValue(1);
var willFocusRoute = this._subRouteFocus[this.state.presentedIndex] || this.state.routeStack[this.state.presentedIndex];
this._emitWillFocus(willFocusRoute);
},
/**
* TODO: Accept callback for spring completion.
* This happens for each frame of either a gesture or a transition. If both are
* happening, we only set values for the transition and the gesture will catch up later
*/
_requestTransitionTo: function(topOfStack) {
if (topOfStack !== this.state.presentedIndex) {
invariant(!this.state.isAnimating, 'Cannot navigate while transitioning');
this.state.fromIndex = this.state.presentedIndex;
this.state.toIndex = topOfStack;
this.spring.setOvershootClampingEnabled(false);
if (AnimationsDebugModule) {
AnimationsDebugModule.startRecordingFps();
}
this._transitionToToIndexWithVelocity(
this.state.sceneConfigStack[this.state.fromIndex].defaultTransitionVelocity
_handleSpringUpdate: function() {
// Prioritize handling transition in progress over a gesture:
if (this.state.transitionFromIndex != null) {
this._transitionBetween(
this.state.transitionFromIndex,
this.state.presentedIndex,
this.spring.getCurrentValue()
);
} else if (this.state.activeGesture != null) {
this._transitionBetween(
this.state.presentedIndex,
this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture),
this.spring.getCurrentValue()
);
}
},
/**
* `onSpring*` spring delegate. Wired up via `spring.addListener(this)`
* This happens at the end of a transition started by transitionTo
*/
onSpringEndStateChange: function() {
if (!this._interactionHandle) {
this._interactionHandle = this.createInteractionHandle();
_completeTransition: function() {
if (this.spring.getCurrentValue() !== 1) {
if (this.state.pendingGestureProgress) {
this.state.pendingGestureProgress = null;
}
return;
}
},
onSpringUpdate: function() {
this._transitionBetween(
this.state.fromIndex,
this.state.toIndex,
this.spring.getCurrentValue()
);
},
onSpringAtRest: function() {
this.state.isAnimating = false;
this._completeTransition();
this._onAnimationEnd();
var presentedIndex = this.state.presentedIndex;
var didFocusRoute = this._subRouteFocus[presentedIndex] || this.state.routeStack[presentedIndex];
this._emitDidFocus(didFocusRoute);
if (AnimationsDebugModule) {
AnimationsDebugModule.stopRecordingFps(Date.now());
}
this.state.transitionFromIndex = null;
this.spring.setCurrentValue(0).setAtRest();
this._hideScenes();
if (this.state.transitionCb) {
this.state.transitionCb();
this.state.transitionCb = null;
}
if (this._interactionHandle) {
this.clearInteractionHandle(this._interactionHandle);
this._interactionHandle = null;
}
},
_completeTransition: function() {
if (this.spring.getCurrentValue() === 1) {
this._onAnimationEnd();
var presentedIndex = this.state.toIndex;
this.state.presentedIndex = presentedIndex;
this.state.fromIndex = presentedIndex;
var didFocusRoute = this._subRouteFocus[presentedIndex] || this.state.routeStack[presentedIndex];
this._emitDidFocus(didFocusRoute);
this._removePoppedRoutes();
if (AnimationsDebugModule) {
AnimationsDebugModule.stopRecordingFps(Date.now());
}
} else {
this.state.fromIndex = this.state.presentedIndex;
this.state.toIndex = this.state.presentedIndex;
if (this.state.pendingGestureProgress) {
this.spring.setEndValue(this.state.pendingGestureProgress);
return;
}
if (this.state.transitionQueue.length) {
var queuedTransition = this.state.transitionQueue.shift();
this._transitionTo(
queuedTransition.destIndex,
queuedTransition.velocity,
null,
queuedTransition.cb
);
}
this._hideOtherScenes(this.state.presentedIndex);
},
_transitionToToIndexWithVelocity: function(v) {
this._configureSpring(
// For visual consistency, the from index is always used to configure the spring
this.state.sceneConfigStack[this.state.fromIndex]
);
this._onAnimationStart();
this.state.isAnimating = true;
this.spring.setVelocity(v);
this.spring.setEndValue(1);
var willFocusRoute = this._subRouteFocus[this.state.toIndex] || this.state.routeStack[this.state.toIndex];
this._emitWillFocus(willFocusRoute);
},
_transitionToFromIndexWithVelocity: function(v) {
this._configureSpring(
this.state.sceneConfigStack[this.state.fromIndex]
);
this.state.isAnimating = true;
this.spring.setVelocity(v);
this.spring.setEndValue(0);
},
_emitDidFocus: function(route) {
@ -608,9 +625,11 @@ var Navigator = React.createClass({
/**
* Does not delete the scenes - merely hides them.
*/
_hideOtherScenes: function(activeIndex) {
_hideScenes: function() {
for (var i = 0; i < this.state.routeStack.length; i++) {
if (i === activeIndex) {
// This gets called when we detach a gesture, so there will not be a
// current gesture, but there might be a transition in progress
if (i === this.state.presentedIndex || i === this.state.transitionFromIndex) {
continue;
}
var sceneRef = 'scene_' + i;
@ -620,22 +639,31 @@ var Navigator = React.createClass({
},
_onAnimationStart: function() {
this._setRenderSceneToHarwareTextureAndroid(this.state.fromIndex, true);
this._setRenderSceneToHarwareTextureAndroid(this.state.toIndex, true);
var fromIndex = this.state.presentedIndex;
var toIndex = this.state.presentedIndex;
if (this.state.transitionFromIndex != null) {
fromIndex = this.state.transitionFromIndex;
} else if (this.state.activeGesture) {
toIndex = this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture);
}
this._setRenderSceneToHarwareTextureAndroid(fromIndex, true);
this._setRenderSceneToHarwareTextureAndroid(toIndex, true);
var navBar = this._navBar;
if (navBar && navBar.onAnimationStart) {
navBar.onAnimationStart(this.state.fromIndex, this.state.toIndex);
navBar.onAnimationStart(fromIndex, toIndex);
}
},
_onAnimationEnd: function() {
this._setRenderSceneToHarwareTextureAndroid(this.state.fromIndex, false);
this._setRenderSceneToHarwareTextureAndroid(this.state.toIndex, false);
var max = this.state.routeStack.length - 1;
for (var index = 0; index <= max; index++) {
this._setRenderSceneToHarwareTextureAndroid(index, false);
}
var navBar = this._navBar;
if (navBar && navBar.onAnimationEnd) {
navBar.onAnimationEnd(this.state.fromIndex, this.state.toIndex);
navBar.onAnimationEnd();
}
},
@ -647,16 +675,6 @@ var Navigator = React.createClass({
viewAtIndex.setNativeProps({renderToHardwareTextureAndroid: shouldRenderToHardwareTexture});
},
/**
* Becomes the responder on touch start (capture) while animating so that it
* blocks all touch interactions inside of it. However, this responder lock
* means nothing more than that. We record if the sole reason for being
* responder is to block interactions (`isResponderOnlyToBlockTouches`).
*/
_handleStartShouldSetPanResponderCapture: function(e, gestureState) {
return this.state.isAnimating;
},
_handleMoveShouldSetPanResponder: function(e, gestureState) {
var currentRoute = this.state.routeStack[this.state.presentedIndex];
var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex];
@ -674,18 +692,12 @@ var Navigator = React.createClass({
_handlePanResponderGrant: function(e, gestureState) {
invariant(
this._expectingGestureGrant || this.state.isAnimating,
this._expectingGestureGrant,
'Responder granted unexpectedly.'
);
this._activeGestureAction = this._expectingGestureGrant;
this._attachGesture(this._expectingGestureGrant);
this._onAnimationStart();
this._expectingGestureGrant = null;
this.state.isResponderOnlyToBlockTouches = this.state.isAnimating;
if (!this.state.isAnimating) {
this.state.fromIndex = this.state.presentedIndex;
var gestureSceneDelta = this._deltaForGestureAction(this._activeGestureAction);
this.state.toIndex = this.state.presentedIndex + gestureSceneDelta;
this._onAnimationStart();
}
},
_deltaForGestureAction: function(gestureAction) {
@ -703,13 +715,13 @@ var Navigator = React.createClass({
_handlePanResponderRelease: function(e, gestureState) {
var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex];
var releaseGestureAction = this._activeGestureAction;
this._activeGestureAction = null;
if (this.state.isResponderOnlyToBlockTouches) {
this.state.isResponderOnlyToBlockTouches = false;
var releaseGestureAction = this.state.activeGesture;
if (!releaseGestureAction) {
// The gesture may have been detached while responder, so there is no action here
return;
}
var releaseGesture = sceneConfig.gestures[releaseGestureAction];
var destIndex = this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture);
if (this.spring.getCurrentValue() === 0) {
// The spring is at zero, so the gesture is already complete
this.spring.setCurrentValue(0).setAtRest();
@ -732,38 +744,86 @@ var Navigator = React.createClass({
var hasGesturedEnoughToComplete = gestureDistance > releaseGesture.fullDistance * releaseGesture.stillCompletionRatio;
transitionVelocity = hasGesturedEnoughToComplete ? releaseGesture.snapVelocity : -releaseGesture.snapVelocity;
}
this.spring.setOvershootClampingEnabled(true);
if (transitionVelocity < 0 || this._doesGestureOverswipe(releaseGestureAction)) {
this._transitionToFromIndexWithVelocity(transitionVelocity);
// This gesture is to an overswiped region or does not have enough velocity to complete
// If we are currently mid-transition, then this gesture was a pending gesture. Because this gesture takes no action, we can stop here
if (this.state.transitionFromIndex == null) {
// There is no current transition, so we need to transition back to the presented index
var transitionBackToPresentedIndex = this.state.presentedIndex;
// slight hack: change the presented index for a moment in order to transitionTo correctly
this.state.presentedIndex = destIndex;
this._transitionTo(
transitionBackToPresentedIndex,
- transitionVelocity,
1 - this.spring.getCurrentValue()
);
}
} else {
this._transitionToToIndexWithVelocity(transitionVelocity);
// The gesture has enough velocity to complete, so we transition to the gesture's destination
this._transitionTo(destIndex, transitionVelocity);
}
this._detachGesture();
},
_handlePanResponderTerminate: function(e, gestureState) {
this._activeGestureAction = null;
this.state.isResponderOnlyToBlockTouches = false;
this._transitionToFromIndexWithVelocity(0);
var destIndex = this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture);
this._detachGesture();
var transitionBackToPresentedIndex = this.state.presentedIndex;
// slight hack: change the presented index for a moment in order to transitionTo correctly
this.state.presentedIndex = destIndex;
this._transitionTo(
transitionBackToPresentedIndex,
null,
1 - this.spring.getCurrentValue()
);
},
_attachGesture: function(gestureId) {
this.state.activeGesture = gestureId;
},
_detachGesture: function() {
this.state.activeGesture = null;
this.state.pendingGestureProgress = null;
this._hideScenes();
},
_handlePanResponderMove: function(e, gestureState) {
if (!this.state.isResponderOnlyToBlockTouches) {
var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex];
var gesture = sceneConfig.gestures[this._activeGestureAction];
var isTravelVertical = gesture.direction === 'top-to-bottom' || gesture.direction === 'bottom-to-top';
var isTravelInverted = gesture.direction === 'right-to-left' || gesture.direction === 'bottom-to-top';
var distance = isTravelVertical ? gestureState.dy : gestureState.dx;
distance = isTravelInverted ? - distance : distance;
var gestureDetectMovement = gesture.gestureDetectMovement;
var nextProgress = (distance - gestureDetectMovement) /
(gesture.fullDistance - gestureDetectMovement);
if (this._doesGestureOverswipe(this._activeGestureAction)) {
var frictionConstant = gesture.overswipe.frictionConstant;
var frictionByDistance = gesture.overswipe.frictionByDistance;
var frictionRatio = 1 / ((frictionConstant) + (Math.abs(nextProgress) * frictionByDistance));
nextProgress *= frictionRatio;
}
this.spring.setCurrentValue(clamp(0, nextProgress, 1));
var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex];
if (this.state.activeGesture) {
var gesture = sceneConfig.gestures[this.state.activeGesture];
return this._moveAttachedGesture(gesture, gestureState);
}
var matchedGesture = this._matchGestureAction(sceneConfig.gestures, gestureState);
if (matchedGesture) {
this._attachGesture(matchedGesture);
}
},
_moveAttachedGesture: function(gesture, gestureState) {
var isTravelVertical = gesture.direction === 'top-to-bottom' || gesture.direction === 'bottom-to-top';
var isTravelInverted = gesture.direction === 'right-to-left' || gesture.direction === 'bottom-to-top';
var distance = isTravelVertical ? gestureState.dy : gestureState.dx;
distance = isTravelInverted ? - distance : distance;
var gestureDetectMovement = gesture.gestureDetectMovement;
var nextProgress = (distance - gestureDetectMovement) /
(gesture.fullDistance - gestureDetectMovement);
if (nextProgress < 0 && gesture.isDetachable) {
this._detachGesture();
}
if (this._doesGestureOverswipe(this.state.activeGesture)) {
var frictionConstant = gesture.overswipe.frictionConstant;
var frictionByDistance = gesture.overswipe.frictionByDistance;
var frictionRatio = 1 / ((frictionConstant) + (Math.abs(nextProgress) * frictionByDistance));
nextProgress *= frictionRatio;
}
nextProgress = clamp(0, nextProgress, 1);
if (this.state.transitionFromIndex != null) {
this.state.pendingGestureProgress = nextProgress;
} else if (this.state.pendingGestureProgress) {
this.spring.setEndValue(nextProgress);
} else {
this.spring.setCurrentValue(nextProgress);
}
},
@ -771,9 +831,6 @@ var Navigator = React.createClass({
if (!gestures) {
return null;
}
if (this.state.isResponderOnlyToBlockTouches || this.state.isAnimating) {
return null;
}
var matchedGesture = null;
GESTURE_ACTIONS.some((gestureName) => {
var gesture = gestures[gestureName];
@ -814,7 +871,7 @@ var Navigator = React.createClass({
return;
}
// Use toIndex animation when we move forwards. Use fromIndex when we move back
var sceneConfigIndex = this.state.presentedIndex < toIndex ? toIndex : fromIndex;
var sceneConfigIndex = fromIndex < toIndex ? toIndex : fromIndex;
var sceneConfig = this.state.sceneConfigStack[sceneConfigIndex];
// this happens for overswiping when there is no scene at toIndex
if (!sceneConfig) {
@ -849,10 +906,6 @@ var Navigator = React.createClass({
this.state.updatingRangeLength = this.state.routeStack.length;
},
_canNavigate: function() {
return !this.state.isAnimating;
},
_getDestIndexWithinBounds: function(n) {
var currentIndex = this.state.presentedIndex;
var destIndex = currentIndex + n;
@ -870,17 +923,13 @@ var Navigator = React.createClass({
_jumpN: function(n) {
var destIndex = this._getDestIndexWithinBounds(n);
if (!this._canNavigate()) {
return; // It's busy animating or transitioning.
}
var requestTransitionAndResetUpdatingRange = () => {
this._requestTransitionTo(destIndex);
this._transitionTo(destIndex);
this._resetUpdatingRange();
};
this.setState({
updatingRangeStart: destIndex,
updatingRangeLength: 1,
toIndex: destIndex,
}, requestTransitionAndResetUpdatingRange);
},
@ -903,9 +952,6 @@ var Navigator = React.createClass({
push: function(route) {
invariant(!!route, 'Must supply route to push');
if (!this._canNavigate()) {
return; // It's busy animating or transitioning.
}
var activeLength = this.state.presentedIndex + 1;
var activeStack = this.state.routeStack.slice(0, activeLength);
var activeIDStack = this.state.idStack.slice(0, activeLength);
@ -916,34 +962,34 @@ var Navigator = React.createClass({
this.props.configureScene(route),
]);
var requestTransitionAndResetUpdatingRange = () => {
this._requestTransitionTo(nextStack.length - 1);
this._transitionTo(nextStack.length - 1);
this._resetUpdatingRange();
};
var navigationState = {
toRoute: route,
fromRoute: this.state.routeStack[this.state.routeStack.length - 1],
};
this.setState({
idStack: nextIDStack,
routeStack: nextStack,
sceneConfigStack: nextAnimationConfigStack,
jumpToIndex: nextStack.length - 1,
updatingRangeStart: nextStack.length - 1,
updatingRangeLength: 1,
}, requestTransitionAndResetUpdatingRange);
},
_popN: function(n) {
if (n === 0 || !this._canNavigate()) {
if (n === 0) {
return;
}
invariant(
this.state.presentedIndex - n >= 0,
'Cannot pop below zero'
);
this.state.jumpToIndex = this.state.presentedIndex - n;
this._requestTransitionTo(
this.state.presentedIndex - n
var popIndex = this.state.presentedIndex - n;
this._transitionTo(
popIndex,
null, // default velocity
null, // no spring jumping
() => {
this._cleanScenesPastIndex(popIndex);
}
);
},
@ -957,7 +1003,7 @@ var Navigator = React.createClass({
* `index` specifies the route in the stack that should be replaced.
* If it's negative, it counts from the back.
*/
replaceAtIndex: function(route, index) {
replaceAtIndex: function(route, index, cb) {
invariant(!!route, 'Must supply route to replace');
if (index < 0) {
index += this.state.routeStack.length;
@ -988,6 +1034,7 @@ var Navigator = React.createClass({
this._emitWillFocus(route);
this._emitDidFocus(route);
}
cb && cb();
});
},
@ -1024,7 +1071,7 @@ var Navigator = React.createClass({
},
replacePreviousAndPop: function(route) {
if (this.state.routeStack.length < 2 || !this._canNavigate()) {
if (this.state.routeStack.length < 2) {
return;
}
this.replacePrevious(route);
@ -1033,10 +1080,9 @@ var Navigator = React.createClass({
resetTo: function(route) {
invariant(!!route, 'Must supply route to push');
if (this._canNavigate()) {
this.replaceAtIndex(route, 0);
this.replaceAtIndex(route, 0, () => {
this.popToRoute(route);
}
});
},
getCurrentRoutes: function() {
@ -1052,8 +1098,8 @@ var Navigator = React.createClass({
this.props.onItemRef && this.props.onItemRef(ref, itemIndex);
},
_removePoppedRoutes: function() {
var newStackLength = this.state.jumpToIndex + 1;
_cleanScenesPastIndex: function(index) {
var newStackLength = index + 1;
// Remove any unneeded rendered routes.
if (newStackLength < this.state.routeStack.length) {
var updatingRangeStart = newStackLength; // One past the top

View File

@ -33,6 +33,8 @@ var StaticContainer = require('StaticContainer.react');
var StyleSheet = require('StyleSheet');
var View = require('View');
var invariant = require('invariant');
var Interpolators = NavigatorBreadcrumbNavigationBarStyles.Interpolators;
var PropTypes = React.PropTypes;
@ -99,6 +101,10 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
var oldDistToCenter = index - fromIndex;
var newDistToCenter = index - toIndex;
var interpolate;
invariant(
Interpolators[index],
'Cannot find breadcrumb interpolators for ' + index
);
if (oldDistToCenter > 0 && newDistToCenter === 0 ||
newDistToCenter > 0 && oldDistToCenter === 0) {
interpolate = Interpolators[index].RightToCenter;
@ -146,10 +152,9 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
}
},
onAnimationEnd: function(fromIndex, toIndex) {
var max = Math.max(fromIndex, toIndex);
var min = Math.min(fromIndex, toIndex);
for (var index = min; index <= max; index++) {
onAnimationEnd: function() {
var max = this.props.navState.routeStack.length - 1;
for (var index = 0; index <= max; index++) {
this._setRenderViewsToHardwareTextureAndroid(index, false);
}
},

View File

@ -101,6 +101,30 @@ var FadeToTheLeft = {
},
};
var FadeIn = {
opacity: {
from: 0,
to: 1,
min: 0.5,
max: 1,
type: 'linear',
extrapolate: false,
round: 100,
},
};
var FadeOut = {
opacity: {
from: 1,
to: 0,
min: 0,
max: 0.5,
type: 'linear',
extrapolate: false,
round: 100,
},
};
var ToTheLeft = {
transformTranslate: {
from: {x: 0, y: 0, z: 0},
@ -115,8 +139,17 @@ var ToTheLeft = {
value: 1.0,
type: 'constant',
},
};
translateX: {
from: 0,
to: -Dimensions.get('window').width,
min: 0,
max: 1,
type: 'linear',
extrapolate: true,
round: PixelRatio.get(),
},
};
var FromTheRight = {
opacity: {
@ -236,6 +269,43 @@ var FromTheFront = {
},
};
var ToTheBackAndroid = {
opacity: {
value: 1,
type: 'constant',
},
};
var FromTheFrontAndroid = {
opacity: {
from: 0,
to: 1,
min: 0,
max: 1,
type: 'linear',
extrapolate: false,
round: 100,
},
transformTranslate: {
from: {x: 0, y: 50, z: 0},
to: {x: 0, y: 0, z: 0},
min: 0,
max: 1,
type: 'linear',
extrapolate: true,
round: PixelRatio.get(),
},
translateY: {
from: 50,
to: 0,
min: 0,
max: 1,
type: 'linear',
extrapolate: true,
round: PixelRatio.get(),
},
};
var BaseOverswipeConfig = {
frictionConstant: 1,
frictionByDistance: 1.5,
@ -243,6 +313,9 @@ var BaseOverswipeConfig = {
var BaseLeftToRightGesture = {
// If the gesture can end and restart during one continuous touch
isDetachable: false,
// How far the swipe must drag to start transitioning
gestureDetectMovement: 2,
@ -316,6 +389,22 @@ var NavigatorSceneConfigs = {
out: buildStyleInterpolator(ToTheBack),
},
},
FloatFromBottomAndroid: {
...BaseConfig,
gestures: null,
animationInterpolators: {
into: buildStyleInterpolator(FromTheFrontAndroid),
out: buildStyleInterpolator(ToTheBackAndroid),
},
},
FadeAndroid: {
...BaseConfig,
gestures: null,
animationInterpolators: {
into: buildStyleInterpolator(FadeIn),
out: buildStyleInterpolator(FadeOut),
},
},
HorizontalSwipeJump: {
...BaseConfig,
gestures: {
@ -323,11 +412,13 @@ var NavigatorSceneConfigs = {
...BaseLeftToRightGesture,
overswipe: BaseOverswipeConfig,
edgeHitWidth: null,
isDetachable: true,
},
jumpForward: {
...BaseRightToLeftGesture,
overswipe: BaseOverswipeConfig,
edgeHitWidth: null,
isDetachable: true,
},
},
animationInterpolators: {

View File

@ -22,6 +22,12 @@ var subscriptions = [];
var updatesEnabled = false;
type GeoOptions = {
timeout: number;
maximumAge: number;
enableHighAccuracy: bool;
}
/**
* You need to include the `NSLocationWhenInUseUsageDescription` key
* in Info.plist to enable geolocation. Geolocation is enabled by default
@ -32,10 +38,14 @@ var updatesEnabled = false;
*/
var Geolocation = {
/*
* Invokes the success callback once with the latest location info. Supported
* options: timeout (ms), maximumAge (ms), enableHighAccuracy (bool)
*/
getCurrentPosition: function(
geo_success: Function,
geo_error?: Function,
geo_options?: Object
geo_options?: GeoOptions
) {
invariant(
typeof geo_success === 'function',
@ -48,7 +58,11 @@ var Geolocation = {
);
},
watchPosition: function(success: Function, error?: Function, options?: Object): number {
/*
* Invokes the success callback whenever the location changes. Supported
* options: timeout (ms), maximumAge (ms), enableHighAccuracy (bool)
*/
watchPosition: function(success: Function, error?: Function, options?: GeoOptions): number {
if (!updatesEnabled) {
RCTLocationObserver.startObserving(options || {});
updatesEnabled = true;

View File

@ -163,12 +163,12 @@ RCT_EXPORT_MODULE()
#pragma mark - Public API
RCT_EXPORT_METHOD(startObserving:(NSDictionary *)optionsJSON)
RCT_EXPORT_METHOD(startObserving:(RCTLocationOptions)options)
{
[self checkLocationConfig];
// Select best options
_observerOptions = [RCTConvert RCTLocationOptions:optionsJSON];
_observerOptions = options;
for (RCTLocationRequest *request in _pendingRequests) {
_observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy);
}
@ -189,7 +189,7 @@ RCT_EXPORT_METHOD(stopObserving)
}
}
RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON
RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
withSuccessCallback:(RCTResponseSenderBlock)successBlock
errorCallback:(RCTResponseSenderBlock)errorBlock)
{
@ -219,7 +219,6 @@ RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON
}
// Check if previous recorded location exists and is good enough
RCTLocationOptions options = [RCTConvert RCTLocationOptions:optionsJSON];
if (_lastLocationEvent &&
CFAbsoluteTimeGetCurrent() - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge &&
[_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] >= options.accuracy) {

View File

@ -31,7 +31,7 @@ var verifyPropTypes = require('verifyPropTypes');
var warning = require('warning');
/**
* A react component for displaying different types of images,
* A React component for displaying different types of images,
* including network images, static resources, temporary local images, and
* images from local disk, such as the camera roll.
*

View File

@ -32,6 +32,12 @@ describe('resolveAssetSource', () => {
expect(resolveAssetSource(source2)).toBe(source2);
});
it('ignores any weird data', () => {
expect(resolveAssetSource(null)).toBe(null);
expect(resolveAssetSource(42)).toBe(null);
expect(resolveAssetSource('nonsense')).toBe(null);
});
describe('bundle was loaded from network', () => {
beforeEach(() => {
SourceCode.scriptURL = 'http://10.0.0.1:8081/main.bundle';

View File

@ -43,10 +43,11 @@ function pickScale(scales, deviceScale) {
return scales[scales.length - 1] || 1;
}
// TODO(frantic):
// * Pick best scale and append @Nx to file path
// * We are currently using httpServerLocation for both http and in-app bundle
function resolveAssetSource(source) {
if (!source || typeof source !== 'object') {
return null;
}
if (!source.__packager_asset) {
return source;
}

View File

@ -16,6 +16,7 @@ var RCTExceptionsManager = require('NativeModules').ExceptionsManager;
var loadSourceMap = require('loadSourceMap');
var parseErrorStack = require('parseErrorStack');
var stringifySafe = require('stringifySafe');
var sourceMapPromise;
@ -25,17 +26,11 @@ type Exception = {
message: string;
}
function handleException(e: Exception) {
var stack = parseErrorStack(e);
console.error(
'Err0r: ' +
'\n stack: \n' + stackToString(stack) +
'\n URL: ' + e.sourceURL +
'\n line: ' + e.line +
'\n message: ' + e.message
);
function reportException(e: Exception, stack?: any) {
if (RCTExceptionsManager) {
if (!stack) {
stack = parseErrorStack(e);
}
RCTExceptionsManager.reportUnhandledException(e.message, stack);
if (__DEV__) {
(sourceMapPromise = sourceMapPromise || loadSourceMap())
@ -50,6 +45,47 @@ function handleException(e: Exception) {
}
}
function handleException(e: Exception) {
var stack = parseErrorStack(e);
var msg =
'Error: ' + e.message +
'\n stack: \n' + stackToString(stack) +
'\n URL: ' + e.sourceURL +
'\n line: ' + e.line +
'\n message: ' + e.message;
if (console.errorOriginal) {
console.errorOriginal(msg);
} else {
console.error(msg);
}
reportException(e, stack);
}
/**
* Shows a redbox with stacktrace for all console.error messages. Disable by
* setting `console.reportErrorsAsExceptions = false;` in your app.
*/
function installConsoleErrorReporter() {
if (console.reportException) {
return; // already installed
}
console.reportException = reportException;
console.errorOriginal = console.error.bind(console);
console.error = function reactConsoleError() {
console.errorOriginal.apply(null, arguments);
if (!console.reportErrorsAsExceptions) {
return;
}
var str = Array.prototype.map.call(arguments, stringifySafe).join(', ');
var error: any = new Error('console.error: ' + str);
error.framesToPop = 1;
reportException(error);
};
if (console.reportErrorsAsExceptions === undefined) {
console.reportErrorsAsExceptions = true; // Individual apps can disable this
}
}
function stackToString(stack) {
var maxLength = Math.max.apply(null, stack.map(frame => frame.methodName.length));
return stack.map(frame => stackFrameToString(frame, maxLength)).join('\n');
@ -71,4 +107,4 @@ function fillSpaces(n) {
return new Array(n + 1).join(' ');
}
module.exports = { handleException };
module.exports = { handleException, installConsoleErrorReporter };

View File

@ -61,7 +61,7 @@ RCT_EXPORT_METHOD(queryData:(NSString *)queryType
responseJSON = @{
@"status": @0,
@"responseHeaders": @{},
@"responseText": [connectionError localizedDescription]
@"responseText": [connectionError localizedDescription] ?: [NSNull null]
};
}

View File

@ -46,7 +46,7 @@ var ReactInstanceMap = require('ReactInstanceMap');
*
* `mountImage`: A way to represent the potential to create lower level
* resources whos `nodeHandle` can be discovered immediately by knowing the
* `rootNodeID`. Today's web react represents this with `innerHTML` annotated
* `rootNodeID`. Today's web React represents this with `innerHTML` annotated
* with DOM ids that match the `rootNodeID`.
*
* Opaque name TodaysWebReact FutureWebWorkerReact ReactNative

View File

@ -191,7 +191,7 @@ var ReactIOSMount = {
* that has been rendered and unmounting it. There should just be one child
* component at this time.
*/
unmountComponentAtNode: function(containerTag: number): bool {
unmountComponentAtNode: function(containerTag: number): boolean {
if (!ReactIOSTagHandles.reactTagIsNativeTopRootID(containerTag)) {
console.error('You cannot render into anything but a top root');
return false;

View File

@ -0,0 +1,398 @@
/**
* 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 WarningBox
*/
'use strict';
var AsyncStorage = require('AsyncStorage');
var EventEmitter = require('EventEmitter');
var Map = require('Map');
var PanResponder = require('PanResponder');
var React = require('React');
var StyleSheet = require('StyleSheet');
var Text = require('Text');
var TouchableOpacity = require('TouchableOpacity');
var View = require('View');
var invariant = require('invariant');
var rebound = require('rebound');
var stringifySafe = require('stringifySafe');
var SCREEN_WIDTH = require('Dimensions').get('window').width;
var IGNORED_WARNINGS_KEY = '__DEV_WARNINGS_IGNORED';
var consoleWarn = console.warn.bind(console);
var warningCounts = new Map();
var ignoredWarnings: Array<string> = [];
var totalWarningCount = 0;
var warningCountEvents = new EventEmitter();
/**
* WarningBox renders warnings on top of the app being developed. Warnings help
* guard against subtle yet significant issues that can impact the quality of
* your application, such as accessibility and memory leaks. This "in your
* face" style of warning allows developers to notice and correct these issues
* as quickly as possible.
*
* The warning box is currently opt-in. Set the following flag to enable it:
*
* `console.yellowBoxEnabled = true;`
*
* If "ignore" is tapped on a warning, the WarningBox will record that warning
* and will not display it again. This is useful for hiding errors that already
* exist or have been introduced elsewhere. To re-enable all of the errors, set
* the following:
*
* `console.yellowBoxResetIgnored = true;`
*
* This can also be set permanently, and ignore will only silence the warnings
* until the next refresh.
*/
if (__DEV__) {
console.warn = function() {
consoleWarn.apply(null, arguments);
if (!console.yellowBoxEnabled) {
return;
}
var warning = Array.prototype.map.call(arguments, stringifySafe).join(' ');
if (!console.yellowBoxResetIgnored &&
ignoredWarnings.indexOf(warning) !== -1) {
return;
}
var count = warningCounts.has(warning) ? warningCounts.get(warning) + 1 : 1;
warningCounts.set(warning, count);
totalWarningCount += 1;
warningCountEvents.emit('count', totalWarningCount);
};
}
function saveIgnoredWarnings() {
AsyncStorage.setItem(
IGNORED_WARNINGS_KEY,
JSON.stringify(ignoredWarnings),
function(err) {
if (err) {
console.warn('Could not save ignored warnings.', err);
}
}
);
}
AsyncStorage.getItem(IGNORED_WARNINGS_KEY, function(err, data) {
if (!err && data && !console.yellowBoxResetIgnored) {
ignoredWarnings = JSON.parse(data);
}
});
var WarningRow = React.createClass({
componentWillMount: function() {
this.springSystem = new rebound.SpringSystem();
this.dismissalSpring = this.springSystem.createSpring();
this.dismissalSpring.setRestSpeedThreshold(0.05);
this.dismissalSpring.setCurrentValue(0);
this.dismissalSpring.addListener({
onSpringUpdate: () => {
var val = this.dismissalSpring.getCurrentValue();
this.text && this.text.setNativeProps({
left: SCREEN_WIDTH * val,
});
this.container && this.container.setNativeProps({
opacity: 1 - val,
});
this.closeButton && this.closeButton.setNativeProps({
opacity: 1 - (val * 5),
});
},
onSpringAtRest: () => {
if (this.dismissalSpring.getCurrentValue()) {
this.collapseSpring.setEndValue(1);
}
},
});
this.collapseSpring = this.springSystem.createSpring();
this.collapseSpring.setRestSpeedThreshold(0.05);
this.collapseSpring.setCurrentValue(0);
this.collapseSpring.getSpringConfig().friction = 20;
this.collapseSpring.getSpringConfig().tension = 200;
this.collapseSpring.addListener({
onSpringUpdate: () => {
var val = this.collapseSpring.getCurrentValue();
this.container && this.container.setNativeProps({
height: Math.abs(46 - (val * 46)),
});
},
onSpringAtRest: () => {
this.props.onDismissed();
},
});
this.panGesture = PanResponder.create({
onStartShouldSetPanResponder: () => {
return !! this.dismissalSpring.getCurrentValue();
},
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
this.isResponderOnlyToBlockTouches =
!! this.dismissalSpring.getCurrentValue();
},
onPanResponderMove: (e, gestureState) => {
if (this.isResponderOnlyToBlockTouches) {
return;
}
this.dismissalSpring.setCurrentValue(gestureState.dx / SCREEN_WIDTH);
},
onPanResponderRelease: (e, gestureState) => {
if (this.isResponderOnlyToBlockTouches) {
return;
}
var gestureCompletion = gestureState.dx / SCREEN_WIDTH;
var doesGestureRelease = (gestureState.vx + gestureCompletion) > 0.5;
this.dismissalSpring.setEndValue(doesGestureRelease ? 1 : 0);
}
});
},
render: function() {
var countText;
if (warningCounts.get(this.props.warning) > 1) {
countText = (
<Text style={styles.bold}>
({warningCounts.get(this.props.warning)}){" "}
</Text>
);
}
return (
<View
style={styles.warningBox}
ref={container => { this.container = container; }}
{...this.panGesture.panHandlers}>
<TouchableOpacity
onPress={this.props.onOpened}>
<View>
<Text
style={styles.warningText}
numberOfLines={2}
ref={text => { this.text = text; }}>
{countText}
{this.props.warning}
</Text>
</View>
</TouchableOpacity>
<View
ref={closeButton => { this.closeButton = closeButton; }}
style={styles.closeButton}>
<TouchableOpacity
onPress={() => {
this.dismissalSpring.setEndValue(1);
}}>
<Text style={styles.closeButtonText}></Text>
</TouchableOpacity>
</View>
</View>
);
}
});
var WarningBoxOpened = React.createClass({
render: function() {
var countText;
if (warningCounts.get(this.props.warning) > 1) {
countText = (
<Text style={styles.bold}>
({warningCounts.get(this.props.warning)}){" "}
</Text>
);
}
return (
<TouchableOpacity
activeOpacity={0.9}
onPress={this.props.onClose}>
<View style={styles.yellowBox}>
<Text style={styles.yellowBoxText}>
{countText}
{this.props.warning}
</Text>
<View style={styles.yellowBoxButtons}>
<View style={styles.yellowBoxButton}>
<TouchableOpacity
onPress={this.props.onDismissed}>
<Text style={styles.yellowBoxButtonText}>
Dismiss
</Text>
</TouchableOpacity>
</View>
<View style={styles.yellowBoxButton}>
<TouchableOpacity
onPress={this.props.onIgnored}>
<Text style={styles.yellowBoxButtonText}>
Ignore
</Text>
</TouchableOpacity>
</View>
</View>
</View>
</TouchableOpacity>
);
},
});
var canMountWarningBox = true;
var WarningBox = React.createClass({
getInitialState: function() {
return {
totalWarningCount,
openWarning: null,
};
},
componentWillMount: function() {
if (console.yellowBoxResetIgnored) {
AsyncStorage.setItem(IGNORED_WARNINGS_KEY, '[]', function(err) {
if (err) {
console.warn('Could not reset ignored warnings.', err);
}
});
ignoredWarnings = [];
}
},
componentDidMount: function() {
invariant(
canMountWarningBox,
'There can only be one WarningBox'
);
canMountWarningBox = false;
warningCountEvents.addListener(
'count',
this._onWarningCount
);
},
componentWillUnmount: function() {
warningCountEvents.removeAllListeners();
canMountWarningBox = true;
},
_onWarningCount: function(totalWarningCount) {
// Must use setImmediate because warnings often happen during render and
// state cannot be set while rendering
setImmediate(() => {
this.setState({ totalWarningCount, });
});
},
_onDismiss: function(warning) {
warningCounts.delete(warning);
this.setState({
openWarning: null,
});
},
render: function() {
if (warningCounts.size === 0) {
return <View />;
}
if (this.state.openWarning) {
return (
<WarningBoxOpened
warning={this.state.openWarning}
onClose={() => {
this.setState({ openWarning: null });
}}
onDismissed={this._onDismiss.bind(this, this.state.openWarning)}
onIgnored={() => {
ignoredWarnings.push(this.state.openWarning);
saveIgnoredWarnings();
this._onDismiss(this.state.openWarning);
}}
/>
);
}
var warningRows = [];
warningCounts.forEach((count, warning) => {
warningRows.push(
<WarningRow
key={warning}
onOpened={() => {
this.setState({ openWarning: warning });
}}
onDismissed={this._onDismiss.bind(this, warning)}
warning={warning}
/>
);
});
return (
<View style={styles.warningContainer}>
{warningRows}
</View>
);
},
});
var styles = StyleSheet.create({
bold: {
fontWeight: 'bold',
},
closeButton: {
position: 'absolute',
right: 0,
height: 46,
width: 46,
},
closeButtonText: {
color: 'white',
fontSize: 32,
position: 'relative',
left: 8,
},
warningContainer: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0
},
warningBox: {
position: 'relative',
backgroundColor: 'rgba(171, 124, 36, 0.9)',
flex: 1,
height: 46,
},
warningText: {
color: 'white',
position: 'absolute',
left: 0,
marginLeft: 15,
marginRight: 46,
top: 7,
},
yellowBox: {
backgroundColor: 'rgba(171, 124, 36, 0.9)',
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
padding: 15,
paddingTop: 35,
},
yellowBoxText: {
color: 'white',
fontSize: 20,
},
yellowBoxButtons: {
flexDirection: 'row',
position: 'absolute',
bottom: 0,
},
yellowBoxButton: {
flex: 1,
padding: 25,
},
yellowBoxButtonText: {
color: 'white',
fontSize: 16,
}
});
module.exports = WarningBox;

View File

@ -11,7 +11,12 @@
*/
'use strict';
require('ExceptionsManager').installConsoleErrorReporter();
var React = require('React');
var StyleSheet = require('StyleSheet');
var View = require('View');
var WarningBox = require('WarningBox');
var invariant = require('invariant');
@ -24,12 +29,27 @@ function renderApplication<D, P, S>(
rootTag,
'Expect to have a valid rootTag, instead got ', rootTag
);
var shouldRenderWarningBox = __DEV__ && console.yellowBoxEnabled;
var warningBox = shouldRenderWarningBox ? <WarningBox /> : null;
React.render(
<RootComponent
{...initialProps}
/>,
<View style={styles.appContainer}>
<RootComponent
{...initialProps}
/>
{warningBox}
</View>,
rootTag
);
}
var styles = StyleSheet.create({
appContainer: {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
},
});
module.exports = renderApplication;

View File

@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
131B6AC01AF0CD0600FFC3E0 /* RCTTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6ABD1AF0CD0600FFC3E0 /* RCTTextView.m */; };
131B6AC11AF0CD0600FFC3E0 /* RCTTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6ABF1AF0CD0600FFC3E0 /* RCTTextViewManager.m */; };
58B511CE1A9E6C5C00147676 /* RCTRawTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511C71A9E6C5C00147676 /* RCTRawTextManager.m */; };
58B511CF1A9E6C5C00147676 /* RCTShadowRawText.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511C91A9E6C5C00147676 /* RCTShadowRawText.m */; };
58B511D01A9E6C5C00147676 /* RCTShadowText.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511CB1A9E6C5C00147676 /* RCTShadowText.m */; };
@ -27,6 +29,10 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
131B6ABC1AF0CD0600FFC3E0 /* RCTTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextView.h; sourceTree = "<group>"; };
131B6ABD1AF0CD0600FFC3E0 /* RCTTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextView.m; sourceTree = "<group>"; };
131B6ABE1AF0CD0600FFC3E0 /* RCTTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextViewManager.h; sourceTree = "<group>"; };
131B6ABF1AF0CD0600FFC3E0 /* RCTTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextViewManager.m; sourceTree = "<group>"; };
58B5119B1A9E6C1200147676 /* libRCTText.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTText.a; sourceTree = BUILT_PRODUCTS_DIR; };
58B511C61A9E6C5C00147676 /* RCTRawTextManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRawTextManager.h; sourceTree = "<group>"; };
58B511C71A9E6C5C00147676 /* RCTRawTextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRawTextManager.m; sourceTree = "<group>"; };
@ -64,6 +70,10 @@
58B512141A9E6EFF00147676 /* RCTText.m */,
58B511CC1A9E6C5C00147676 /* RCTTextManager.h */,
58B511CD1A9E6C5C00147676 /* RCTTextManager.m */,
131B6ABC1AF0CD0600FFC3E0 /* RCTTextView.h */,
131B6ABD1AF0CD0600FFC3E0 /* RCTTextView.m */,
131B6ABE1AF0CD0600FFC3E0 /* RCTTextViewManager.h */,
131B6ABF1AF0CD0600FFC3E0 /* RCTTextViewManager.m */,
58B5119C1A9E6C1200147676 /* Products */,
);
indentWidth = 2;
@ -135,8 +145,10 @@
buildActionMask = 2147483647;
files = (
58B511D11A9E6C5C00147676 /* RCTTextManager.m in Sources */,
131B6AC01AF0CD0600FFC3E0 /* RCTTextView.m in Sources */,
58B511CE1A9E6C5C00147676 /* RCTRawTextManager.m in Sources */,
58B512161A9E6EFF00147676 /* RCTText.m in Sources */,
131B6AC11AF0CD0600FFC3E0 /* RCTTextViewManager.m in Sources */,
58B511CF1A9E6C5C00147676 /* RCTShadowRawText.m in Sources */,
58B511D01A9E6C5C00147676 /* RCTShadowText.m in Sources */,
);

View File

@ -12,4 +12,3 @@
@interface RCTTextManager : RCTViewManager
@end

View File

@ -123,7 +123,7 @@ RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
UIEdgeInsets padding = shadowView.paddingAsInsets;
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
RCTText *text = (RCTText *)viewRegistry[reactTag];
RCTText *text = viewRegistry[reactTag];
text.contentInset = padding;
text.layoutManager = shadowView.layoutManager;
text.textContainer = shadowView.textContainer;

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import "RCTView.h"
#import "UIView+React.h"
@class RCTEventDispatcher;
@interface RCTTextView : RCTView <UITextViewDelegate>
@property (nonatomic, assign) BOOL autoCorrect;
@property (nonatomic, assign) BOOL clearTextOnFocus;
@property (nonatomic, assign) BOOL selectTextOnFocus;
@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
@property (nonatomic, strong) UIColor *placeholderTextColor;
@property (nonatomic, assign) UIFont *font;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@end

View File

@ -0,0 +1,216 @@
/**
* 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 "RCTTextView.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTUtils.h"
#import "UIView+React.h"
@implementation RCTTextView
{
RCTEventDispatcher *_eventDispatcher;
BOOL _jsRequestingFirstResponder;
NSString *_placeholder;
UITextView *_placeholderView;
UITextView *_textView;
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super initWithFrame:CGRectZero])) {
_contentInset = UIEdgeInsetsZero;
_eventDispatcher = eventDispatcher;
_placeholderTextColor = [self defaultPlaceholderTextColor];
_textView = [[UITextView alloc] initWithFrame:self.bounds];
_textView.backgroundColor = [UIColor clearColor];
_textView.delegate = self;
[self addSubview:_textView];
}
return self;
}
- (void)updateFrames
{
// Adjust the insets so that they are as close as possible to single-line
// RCTTextField defaults
UIEdgeInsets adjustedInset = (UIEdgeInsets){
_contentInset.top - 5, _contentInset.left - 4,
_contentInset.bottom, _contentInset.right
};
[_textView setFrame:UIEdgeInsetsInsetRect(self.bounds, adjustedInset)];
[_placeholderView setFrame:UIEdgeInsetsInsetRect(self.bounds, adjustedInset)];
}
- (void)updatePlaceholder
{
[_placeholderView removeFromSuperview];
_placeholderView = nil;
if (_placeholder) {
_placeholderView = [[UITextView alloc] initWithFrame:self.bounds];
_placeholderView.backgroundColor = [UIColor clearColor];
_placeholderView.scrollEnabled = false;
_placeholderView.attributedText =
[[NSAttributedString alloc] initWithString:_placeholder attributes:@{
NSFontAttributeName : (_textView.font ? _textView.font : [self defaultPlaceholderFont]),
NSForegroundColorAttributeName : _placeholderTextColor
}];
[self insertSubview:_placeholderView belowSubview:_textView];
[self _setPlaceholderVisibility];
}
}
- (void)setFont:(UIFont *)font
{
_font = font;
_textView.font = _font;
[self updatePlaceholder];
}
- (void)setTextColor:(UIColor *)textColor
{
_textView.textColor = textColor;
}
- (void)setPlaceholder:(NSString *)placeholder
{
_placeholder = placeholder;
[self updatePlaceholder];
}
- (void)setPlaceholderTextColor:(UIColor *)placeholderTextColor
{
if (placeholderTextColor) {
_placeholderTextColor = placeholderTextColor;
} else {
_placeholderTextColor = [self defaultPlaceholderTextColor];
}
[self updatePlaceholder];
}
- (void)setContentInset:(UIEdgeInsets)contentInset
{
_contentInset = contentInset;
[self updateFrames];
}
- (void)setText:(NSString *)text
{
if (![text isEqualToString:_textView.text]) {
[_textView setText:text];
[self _setPlaceholderVisibility];
}
}
- (void)_setPlaceholderVisibility
{
if (_textView.text.length > 0) {
[_placeholderView setHidden:YES];
} else {
[_placeholderView setHidden:NO];
}
}
- (void)setAutoCorrect:(BOOL)autoCorrect
{
_textView.autocorrectionType = (autoCorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo);
}
- (BOOL)autoCorrect
{
return _textView.autocorrectionType == UITextAutocorrectionTypeYes;
}
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
if (_selectTextOnFocus) {
dispatch_async(dispatch_get_main_queue(), ^{
[textView selectAll:nil];
});
}
return YES;
}
- (void)textViewDidBeginEditing:(UITextView *)textView
{
if (_clearTextOnFocus) {
[_textView setText:@""];
_textView.text = @"";
[self _setPlaceholderVisibility];
}
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
reactTag:self.reactTag
text:textView.text];
}
- (void)textViewDidChange:(UITextView *)textView
{
[self _setPlaceholderVisibility];
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
reactTag:self.reactTag
text:textView.text];
}
- (void)textViewDidEndEditing:(UITextView *)textView
{
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
reactTag:self.reactTag
text:textView.text];
}
- (BOOL)becomeFirstResponder
{
_jsRequestingFirstResponder = YES;
BOOL result = [_textView becomeFirstResponder];
_jsRequestingFirstResponder = NO;
return result;
}
- (BOOL)resignFirstResponder
{
[super resignFirstResponder];
BOOL result = [_textView resignFirstResponder];
if (result) {
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
reactTag:self.reactTag
text:_textView.text];
}
return result;
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self updateFrames];
}
- (BOOL)canBecomeFirstResponder
{
return _jsRequestingFirstResponder;
}
- (UIFont *)defaultPlaceholderFont
{
return [UIFont fontWithName:@"Helvetica" size:17];
}
- (UIColor *)defaultPlaceholderTextColor
{
return [UIColor colorWithRed:0.0/255.0 green:0.0/255.0 blue:0.098/255.0 alpha:0.22];
}
@end

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 RCTTextViewManager : RCTViewManager
@end

View File

@ -0,0 +1,65 @@
/**
* 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 "RCTTextViewManager.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTShadowView.h"
#import "RCTSparseArray.h"
#import "RCTTextView.h"
@implementation RCTTextViewManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[RCTTextView alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
}
RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL)
RCT_REMAP_VIEW_PROPERTY(editable, textView.editable, BOOL)
RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)
RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(text, NSString)
RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL)
RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
RCT_REMAP_VIEW_PROPERTY(keyboardType, textView.keyboardType, UIKeyboardType)
RCT_REMAP_VIEW_PROPERTY(returnKeyType, textView.returnKeyType, UIReturnKeyType)
RCT_REMAP_VIEW_PROPERTY(enablesReturnKeyAutomatically, textView.enablesReturnKeyAutomatically, BOOL)
RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor)
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, textView.autocapitalizationType, UITextAutocapitalizationType)
RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextView)
{
view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)];
}
RCT_CUSTOM_VIEW_PROPERTY(fontWeight, NSString, RCTTextView)
{
view.font = [RCTConvert UIFont:view.font withWeight:json]; // defaults to normal
}
RCT_CUSTOM_VIEW_PROPERTY(fontStyle, NSString, RCTTextView)
{
view.font = [RCTConvert UIFont:view.font withStyle:json]; // defaults to normal
}
RCT_CUSTOM_VIEW_PROPERTY(fontFamily, NSString, RCTTextView)
{
view.font = [RCTConvert UIFont:view.font withFamily:json ?: defaultView.font.familyName];
}
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView
{
NSNumber *reactTag = shadowView.reactTag;
UIEdgeInsets padding = shadowView.paddingAsInsets;
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
((RCTTextView *)viewRegistry[reactTag]).contentInset = padding;
};
}
@end

View File

@ -33,7 +33,7 @@ var viewConfig = {
};
/**
* A react component for displaying text which supports nesting,
* A React component for displaying text which supports nesting,
* styling, and touch handling. In the following example, the nested title and
* body text will inherit the `fontFamily` from `styles.baseText`, but the title
* provides its own additional styles. The title and body will stack on top of

View File

@ -20,7 +20,7 @@ var dimensions = NativeModules.UIManager.Dimensions;
// We calculate the window dimensions in JS so that we don't encounter loss of
// precision in transferring the dimensions (which could be non-integers) over
// the bridge.
if (dimensions.windowPhysicalPixels) {
if (dimensions && dimensions.windowPhysicalPixels) {
// parse/stringify => Clone hack
dimensions = JSON.parse(JSON.stringify(dimensions));

View File

@ -21,6 +21,9 @@ var JSTimersExecution = require('JSTimersExecution');
var INTERNAL_ERROR = 'Error in MessageQueue implementation';
// Prints all bridge traffic to console.log
var DEBUG_SPY_MODE = false;
type ModulesConfig = {
[key:string]: {
moduleID: number;
@ -263,6 +266,9 @@ var MessageQueueMixin = {
'both the success callback and the error callback.',
cbID
);
if (DEBUG_SPY_MODE) {
console.log('N->JS: Callback#' + cbID + '(' + JSON.stringify(args) + ')');
}
cb.apply(scope, args);
} catch(ie_requires_catch) {
throw ie_requires_catch;
@ -292,6 +298,11 @@ var MessageQueueMixin = {
var moduleName = this._localModuleIDToModuleName[moduleID];
var methodName = this._localModuleNameToMethodIDToName[moduleName][methodID];
if (DEBUG_SPY_MODE) {
console.log(
'N->JS: ' + moduleName + '.' + methodName +
'(' + JSON.stringify(params) + ')');
}
var ret = jsCall(this._requireFunc(moduleName), methodName, params);
return ret;
@ -460,6 +471,17 @@ var MessageQueueMixin = {
var ret = currentOutgoingItems[REQUEST_MODULE_IDS].length ||
currentOutgoingItems[RESPONSE_RETURN_VALUES].length ? currentOutgoingItems : null;
if (DEBUG_SPY_MODE && ret) {
for (var i = 0; i < currentOutgoingItems[0].length; i++) {
var moduleName = this._remoteModuleIDToModuleName[currentOutgoingItems[0][i]];
var methodName =
this._remoteModuleNameToMethodIDToName[moduleName][currentOutgoingItems[1][i]];
console.log(
'JS->N: ' + moduleName + '.' + methodName +
'(' + JSON.stringify(currentOutgoingItems[2][i]) + ')');
}
}
return ret;
},

View File

@ -0,0 +1,42 @@
/**
* 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 stringifySafe
* @flow
*/
'use strict';
/**
* Tries to stringify with JSON.stringify and toString, but catches exceptions
* (e.g. from circular objects) and always returns a string and never throws.
*/
function stringifySafe(arg: any): string {
var ret;
if (arg === undefined) {
ret = 'undefined';
} else if (arg === null) {
ret = 'null';
} else if (typeof arg === 'string') {
ret = '"' + arg + '"';
} else {
// Perform a try catch, just in case the object has a circular
// reference or stringify throws for some other reason.
try {
ret = JSON.stringify(arg);
} catch (e) {
if (typeof arg.toString === 'function') {
try {
ret = arg.toString();
} catch (E) {}
}
}
}
return ret || '["' + typeof arg + '" failed to stringify]';
}
module.exports = stringifySafe;

View File

@ -27,6 +27,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
NavigatorIOS: require('NavigatorIOS'),
PickerIOS: require('PickerIOS'),
Navigator: require('Navigator'),
SegmentedControlIOS: require('SegmentedControlIOS'),
ScrollView: require('ScrollView'),
SliderIOS: require('SliderIOS'),
SwitchIOS: require('SwitchIOS'),

View File

@ -19,7 +19,7 @@
* worker thread.
*
* The only other requirement of a platform/environment is that it always be
* possible to extract the react rootNodeID in a blocking manner (see
* possible to extract the React rootNodeID in a blocking manner (see
* `getRootNodeID`).
*
* +------------------+ +------------------+ +------------------+

View File

@ -436,6 +436,7 @@ var TouchableMixin = {
if (isTouchWithinActive) {
this._receiveSignal(Signals.ENTER_PRESS_RECT, e);
} else {
this._cancelLongPressDelayTimeout();
this._receiveSignal(Signals.LEAVE_PRESS_RECT, e);
}
},

View File

@ -31,6 +31,8 @@
NSString *const RCTReloadNotification = @"RCTReloadNotification";
NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
dispatch_queue_t const RCTJSThread = nil;
/**
* Must be kept in sync with `MessageQueue.js`.
*/
@ -189,6 +191,7 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
superclass = class_getSuperclass(superclass);
}
}
free(classes);
}
});
@ -353,7 +356,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
#define RCT_CONVERT_CASE(_value, _type) \
case _value: { \
_type (*convert)(id, SEL, id) = (typeof(convert))[RCTConvert methodForSelector:selector]; \
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
break; \
}
@ -375,12 +378,27 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
RCT_CONVERT_CASE('B', BOOL)
RCT_CONVERT_CASE('@', id)
RCT_CONVERT_CASE('^', void *)
case '{':
RCTAssert(NO, @"Argument %zd of %C[%@ %@] is defined as %@, however RCT_EXPORT_METHOD() "
"does not currently support struct-type arguments.", i - 2,
[reactMethodName characterAtIndex:0], _moduleClassName,
objCMethodName, argumentName);
break;
case '{': {
[argumentBlocks addObject:^(RCTBridge *bridge, NSNumber *context, NSInvocation *invocation, NSUInteger index, id json) {
NSUInteger size;
NSGetSizeAndAlignment(argumentType, &size, NULL);
void *returnValue = malloc(size);
NSMethodSignature *methodSignature = [RCTConvert methodSignatureForSelector:selector];
NSInvocation *_invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[_invocation setTarget:[RCTConvert class]];
[_invocation setSelector:selector];
[_invocation setArgument:&json atIndex:2];
[_invocation invoke];
[_invocation getReturnValue:returnValue];
[invocation setArgument:returnValue atIndex:index];
free(returnValue);
}];
break;
}
default:
defaultCase(argumentType);
}
@ -436,6 +454,10 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
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);
}
@ -650,6 +672,8 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
*/
static NSMutableDictionary *RCTLocalModuleIDs;
static NSMutableDictionary *RCTLocalMethodIDs;
static NSMutableArray *RCTLocalModuleNames;
static NSMutableArray *RCTLocalMethodNames;
static NSDictionary *RCTLocalModulesConfig()
{
static NSMutableDictionary *localModules;
@ -658,6 +682,8 @@ static NSDictionary *RCTLocalModulesConfig()
RCTLocalModuleIDs = [[NSMutableDictionary alloc] init];
RCTLocalMethodIDs = [[NSMutableDictionary alloc] init];
RCTLocalModuleNames = [[NSMutableArray alloc] init];
RCTLocalMethodNames = [[NSMutableArray alloc] init];
localModules = [[NSMutableDictionary alloc] init];
for (NSString *moduleDotMethod in RCTJSMethods()) {
@ -689,6 +715,8 @@ static NSDictionary *RCTLocalModulesConfig()
// Add module and method lookup
RCTLocalModuleIDs[moduleDotMethod] = module[@"moduleID"];
RCTLocalMethodIDs[moduleDotMethod] = methods[methodName][@"methodID"];
[RCTLocalModuleNames addObject:moduleName];
[RCTLocalMethodNames addObject:methodName];
}
});
@ -795,6 +823,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
_bundleURL = bundleURL;
_moduleProvider = block;
_launchOptions = [launchOptions copy];
[self setUp];
[self bindKeys];
}
@ -872,6 +901,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
dispatch_queue_t queue = [module methodQueue];
if (queue) {
_queuesByID[moduleID] = queue;
} else {
_queuesByID[moduleID] = [NSNull null];
}
}
}];
@ -1047,7 +1078,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
#pragma mark - RCTBridge methods
/**
* Like JS::call, for objective-c.
* Public. Can be invoked from any thread.
*/
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
{
@ -1058,12 +1089,10 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);
if (!_loading) {
[self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, args ?: @[]]
context:RCTGetExecutorID(_javaScriptExecutor)];
}
[self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, args ?: @[]]
context:RCTGetExecutorID(_javaScriptExecutor)];
}
/**
@ -1081,10 +1110,17 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
if (!_loading) {
#if BATCHED_BRIDGE
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, @[@[timer]]]
context:RCTGetExecutorID(_javaScriptExecutor)];
dispatch_block_t block = ^{
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, @[@[timer]]]
context:RCTGetExecutorID(_javaScriptExecutor)];
};
if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) {
[_javaScriptExecutor executeAsyncBlockOnJavaScriptQueue:block];
} else {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
}
#else
@ -1128,33 +1164,93 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
#pragma mark - Payload Generation
- (void)dispatchBlock:(dispatch_block_t)block forModule:(NSNumber *)moduleID
{
id queue = _queuesByID[moduleID];
if (queue == [NSNull null]) {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
} else {
dispatch_async(queue ?: _methodQueue, block);
}
}
/**
* Called by enqueueJSCall from any thread, or from _immediatelyCallTimer,
* on the JS thread, but only in non-batched mode.
*/
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
{
#if BATCHED_BRIDGE
RCTProfileBeginEvent();
if ([module isEqualToString:@"RCTEventEmitter"]) {
for (NSDictionary *call in _scheduledCalls) {
if ([call[@"module"] isEqualToString:module] && [call[@"method"] isEqualToString:method] && [call[@"args"][0] isEqualToString:args[0]]) {
[_scheduledCalls removeObject:call];
__weak NSMutableArray *weakScheduledCalls = _scheduledCalls;
__weak RCTSparseArray *weakScheduledCallbacks = _scheduledCallbacks;
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
RCTProfileBeginEvent();
NSMutableArray *scheduledCalls = weakScheduledCalls;
RCTSparseArray *scheduledCallbacks = weakScheduledCallbacks;
if (!scheduledCalls || !scheduledCallbacks) {
return;
}
/**
* Event deduping
*
* Right now we make a lot of assumptions about the arguments structure
* so just iterate if it's a `callFunctionReturnFlushedQueue()`
*/
if ([method isEqualToString:@"callFunctionReturnFlushedQueue"]) {
NSString *moduleName = RCTLocalModuleNames[[args[0] integerValue]];
/**
* Keep going if it any event emmiter, e.g. RCT(Device|NativeApp)?EventEmitter
*/
if ([moduleName hasSuffix:@"EventEmitter"]) {
for (NSDictionary *call in [scheduledCalls copy]) {
NSArray *callArgs = call[@"args"];
/**
* If it's the same module && method call on the bridge &&
* the same EventEmitter module && method
*/
if (
[call[@"module"] isEqualToString:module] &&
[call[@"method"] isEqualToString:method] &&
[callArgs[0] isEqual:args[0]] &&
[callArgs[1] isEqual:args[1]]
) {
/**
* args[2] contains the actual arguments for the event call, where
* args[2][0] is the target for RCTEventEmitter or the eventName
* for the other EventEmitters
* if RCTEventEmitter we need to compare args[2][1] that will be
* the eventName
*/
if (
[args[2][0] isEqual:callArgs[2][0]] &&
([moduleName isEqualToString:@"RCTEventEmitter"] ? [args[2][1] isEqual:callArgs[2][1]] : YES)
) {
[scheduledCalls removeObject:call];
}
}
}
}
}
}
id call = @{
@"module": module,
@"method": method,
@"args": args,
@"context": context ?: @0,
};
id call = @{
@"module": module,
@"method": method,
@"args": args,
@"context": context ?: @0,
};
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
_scheduledCallbacks[args[0]] = call;
} else {
[_scheduledCalls addObject:call];
}
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
scheduledCallbacks[args[0]] = call;
} else {
[scheduledCalls addObject:call];
}
RCTProfileEndEvent(@"enqueue_call", @"objc_call", call);
RCTProfileEndEvent(@"enqueue_call", @"objc_call", call);
}];
}
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
@ -1235,10 +1331,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
// TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case?
[_modulesByID enumerateObjectsUsingBlock:^(id<RCTBridgeModule> module, NSNumber *moduleID, BOOL *stop) {
if ([module respondsToSelector:@selector(batchDidComplete)]) {
dispatch_queue_t queue = _queuesByID[moduleID];
dispatch_async(queue ?: _methodQueue, ^{
[self dispatchBlock:^{
[module batchDidComplete];
});
} forModule:moduleID];
}
}];
}
@ -1273,8 +1368,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
__weak RCTBridge *weakSelf = self;
dispatch_queue_t queue = _queuesByID[moduleID];
dispatch_async(queue ?: _methodQueue, ^{
[self dispatchBlock:^{
RCTProfileBeginEvent();
__strong RCTBridge *strongSelf = weakSelf;
@ -1303,7 +1397,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
@"method": method.JSMethodName,
@"selector": NSStringFromSelector(method.selector),
});
});
} forModule:@(moduleID)];
return YES;
}

View File

@ -17,6 +17,16 @@
*/
typedef void (^RCTResponseSenderBlock)(NSArray *response);
/**
* This constant can be returned from +methodQueue to force module
* methods to be called on the JavaScript thread. This can have serious
* implications for performance, so only use this if you're sure it's what
* you need.
*
* NOTE: RCTJSThread is not a real libdispatch queue
*/
extern const dispatch_queue_t RCTJSThread;
/**
* Provides the interface needed to register a bridge module.
*/

View File

@ -49,11 +49,11 @@ RCT_CONVERTER(NSString *, NSString, description)
});
NSNumber *number = [formatter numberFromString:json];
if (!number) {
RCTLogError(@"JSON String '%@' could not be interpreted as a number", json);
RCTLogConvertError(json, "a number");
}
return number;
} else if (json && json != [NSNull null]) {
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a number", json, [json classForCoder]);
RCTLogConvertError(json, "a number");
}
return nil;
}
@ -66,30 +66,38 @@ RCT_CONVERTER(NSString *, NSString, description)
+ (NSURL *)NSURL:(id)json
{
if (!json || json == (id)kCFNull) {
NSString *path = [self NSString:json];
if (!path.length) {
return nil;
}
if (![json isKindOfClass:[NSString class]]) {
RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json classForCoder], json);
return nil;
}
@try { // NSURL has a history of crashing with bad input, so let's be safe
NSString *path = json;
if ([path isAbsolutePath])
{
NSURL *URL = [NSURL URLWithString:path];
if (URL.scheme) { // Was a well-formed absolute URL
return URL;
}
// Check if it has a scheme
if ([path rangeOfString:@"[a-zA-Z][a-zA-Z._-]+:" options:NSRegularExpressionSearch].location == 0) {
path = [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
URL = [NSURL URLWithString:path];
if (URL) {
return URL;
}
}
// Assume that it's a local path
path = [path stringByRemovingPercentEncoding];
if (![path isAbsolutePath]) {
path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:path];
}
return [NSURL fileURLWithPath:path];
}
else if ([path length])
{
NSURL *URL = [NSURL URLWithString:path relativeToURL:[[NSBundle mainBundle] resourceURL]];
if ([URL isFileURL] && ![[NSFileManager defaultManager] fileExistsAtPath:[URL path]]) {
RCTLogWarn(@"The file '%@' does not exist", URL);
return nil;
}
return URL;
@catch (__unused NSException *e) {
RCTLogConvertError(json, "a valid URL");
return nil;
}
return nil;
}
+ (NSURLRequest *)NSURLRequest:(id)json
@ -112,11 +120,12 @@ RCT_CONVERTER(NSString *, NSString, description)
});
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);
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 classForCoder]);
RCTLogConvertError(json, "a date");
}
return nil;
}
@ -698,50 +707,65 @@ static BOOL RCTFontIsCondensed(UIFont *font)
const RCTFontWeight RCTDefaultFontWeight = UIFontWeightRegular;
const CGFloat RCTDefaultFontSize = 14;
// Get existing properties
// Initialize properties to defaults
CGFloat fontSize = RCTDefaultFontSize;
RCTFontWeight fontWeight = RCTDefaultFontWeight;
NSString *familyName = RCTDefaultFontFamily;
BOOL isItalic = NO;
BOOL isCondensed = NO;
RCTFontWeight fontWeight = RCTDefaultFontWeight;
if (font) {
family = font.familyName;
familyName = font.familyName ?: RCTDefaultFontFamily;
fontSize = font.pointSize ?: RCTDefaultFontSize;
fontWeight = RCTWeightOfFont(font);
isItalic = RCTFontIsItalic(font);
isCondensed = RCTFontIsCondensed(font);
}
// Get font size
fontSize = [self CGFloat:size] ?: fontSize;
// Get font family
familyName = [self NSString:family] ?: familyName;
// Get font style
if (style) {
isItalic = [self RCTFontStyle:style];
}
// Get font size
CGFloat fontSize = [self CGFloat:size] ?: RCTDefaultFontSize;
// Get font family
NSString *familyName = [self NSString:family] ?: RCTDefaultFontFamily;
if ([UIFont fontNamesForFamilyName:familyName].count == 0) {
font = [UIFont fontWithName:familyName size:fontSize];
if (font) {
// It's actually a font name, not a font family name,
// but we'll do what was meant, not what was said.
familyName = font.familyName;
NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
fontWeight = [traits[UIFontWeightTrait] doubleValue];
} else {
// Not a valid font or family
RCTLogError(@"Unrecognized font family '%@'", familyName);
familyName = RCTDefaultFontFamily;
}
}
// Get font weight
if (weight) {
fontWeight = [self RCTFontWeight:weight];
}
// Get closest match
UIFont *bestMatch = font;
CGFloat closestWeight = font ? RCTWeightOfFont(font) : INFINITY;
// Gracefully handle being given a font name rather than font family, for
// example: "Helvetica Light Oblique" rather than just "Helvetica".
if ([UIFont fontNamesForFamilyName:familyName].count == 0) {
font = [UIFont fontWithName:familyName size:fontSize];
if (font) {
// It's actually a font name, not a font family name,
// but we'll do what was meant, not what was said.
familyName = font.familyName;
fontWeight = RCTWeightOfFont(font);
isItalic = RCTFontIsItalic(font);
isCondensed = RCTFontIsCondensed(font);
} else {
// Not a valid font or family
RCTLogError(@"Unrecognized font family '%@'", familyName);
familyName = RCTDefaultFontFamily;
}
}
// Get the closest font that matches the given weight for the fontFamily
UIFont *bestMatch = [UIFont fontWithName:font.fontName size: fontSize];
CGFloat closestWeight;
if (font && [font.familyName isEqualToString: familyName]) {
closestWeight = RCTWeightOfFont(font);
} else {
closestWeight = INFINITY;
}
for (NSString *name in [UIFont fontNamesForFamilyName:familyName]) {
UIFont *match = [UIFont fontWithName:name size:fontSize];
if (isItalic == RCTFontIsItalic(match) &&

View File

@ -50,7 +50,7 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
/**
* Send a user input event. The body dictionary must contain a "target"
* parameter, representing the react tag of the view sending the event
* parameter, representing the React tag of the view sending the event
*/
- (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body;

View File

@ -44,7 +44,7 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveEvent);
- (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body
{
RCTAssert([body[@"target"] isKindOfClass:[NSNumber class]],
@"Event body dictionary must include a 'target' property containing a react tag");
@"Event body dictionary must include a 'target' property containing a React tag");
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveEvent"
args:body ? @[body[@"target"], name, body] : @[body[@"target"], name]];

View File

@ -49,6 +49,15 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
*/
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block;
@optional
/**
* Special case for Timers + ContextExecutor - instead of the default
* if jsthread then call else dispatch call on jsthread
* ensure the call is made async on the jsthread
*/
- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block;
@end
static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID";

View File

@ -10,47 +10,15 @@
#import "RCTJavaScriptLoader.h"
#import "RCTBridge.h"
#import "RCTInvalidating.h"
#import "RCTLog.h"
#import "RCTRedBox.h"
#import "RCTConvert.h"
#import "RCTSourceCode.h"
#import "RCTUtils.h"
#define NO_REMOTE_MODULE @"Could not fetch module bundle %@. Ensure node server is running.\n\nIf it timed out, try reloading."
#define NO_LOCAL_BUNDLE @"Could not load local bundle %@. Ensure file exists."
#define CACHE_DIR @"RCTJSBundleCache"
#pragma mark - Application Engine
/**
* TODO:
* - Add window resize rotation events matching the DOM API.
* - Device pixel ration hooks.
* - Source maps.
*/
@implementation RCTJavaScriptLoader
{
__weak RCTBridge *_bridge;
}
/**
* `CADisplayLink` code copied from Ejecta but we've placed the JavaScriptCore
* engine in its own dedicated thread.
*
* TODO: Try adding to the `RCTJavaScriptExecutor`'s thread runloop. Removes one
* additional GCD dispatch per frame and likely makes it so that other UIThread
* operations don't delay the dispatch (so we can begin working in JS much
* faster.) Event handling must still be sent via a GCD dispatch, of course.
*
* We must add the display link to two runloops in order to get setTimeouts to
* fire during scrolling. (`NSDefaultRunLoopMode` and `UITrackingRunLoopMode`)
* TODO: We can invent a `requestAnimationFrame` and
* `requestAvailableAnimationFrame` to control if callbacks can be fired during
* an animation.
* http://stackoverflow.com/questions/12622800/why-does-uiscrollview-pause-my-cadisplaylink
*
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if ((self = [super init])) {
@ -61,92 +29,86 @@
- (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onComplete
{
if (scriptURL == nil) {
// Sanitize the script URL
scriptURL = [RCTConvert NSURL:scriptURL.absoluteString];
if (!scriptURL ||
([scriptURL isFileURL] && ![[NSFileManager defaultManager] fileExistsAtPath:scriptURL.path])) {
NSError *error = [NSError errorWithDomain:@"JavaScriptLoader" code:1 userInfo:@{
NSLocalizedDescriptionKey: @"No script URL provided"
NSLocalizedDescriptionKey: scriptURL ? [NSString stringWithFormat:@"Script at '%@' could not be found.", scriptURL] : @"No script URL provided"
}];
onComplete(error);
return;
}
if ([scriptURL isFileURL]) {
NSString *bundlePath = [[NSBundle bundleForClass:[self class]] resourcePath];
NSString *localPath = [scriptURL.absoluteString substringFromIndex:@"file://".length];
if (![localPath hasPrefix:bundlePath]) {
NSString *absolutePath = [NSString stringWithFormat:@"%@/%@", bundlePath, localPath];
scriptURL = [NSURL fileURLWithPath:absolutePath];
}
}
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:scriptURL completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
// Handle general request errors
if (error) {
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]];
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: desc,
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
NSUnderlyingErrorKey: error,
};
error = [NSError errorWithDomain:@"JSServer"
code:error.code
userInfo:userInfo];
}
onComplete(error);
return;
}
// Handle general request errors
if (error) {
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]];
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: desc,
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
NSUnderlyingErrorKey: error,
};
error = [NSError errorWithDomain:@"JSServer"
code:error.code
userInfo:userInfo];
}
onComplete(error);
return;
}
// Parse response as text
NSStringEncoding encoding = NSUTF8StringEncoding;
if (response.textEncodingName != nil) {
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
if (cfEncoding != kCFStringEncodingInvalidId) {
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
}
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
// Parse response as text
NSStringEncoding encoding = NSUTF8StringEncoding;
if (response.textEncodingName != nil) {
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
if (cfEncoding != kCFStringEncodingInvalidId) {
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
}
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
// Handle HTTP errors
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
NSDictionary *userInfo;
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
if ([errorDetails isKindOfClass:[NSDictionary class]] &&
[errorDetails[@"errors"] isKindOfClass:[NSArray class]]) {
NSMutableArray *fakeStack = [[NSMutableArray alloc] init];
for (NSDictionary *err in errorDetails[@"errors"]) {
[fakeStack addObject: @{
@"methodName": err[@"description"] ?: @"",
@"file": err[@"filename"] ?: @"",
@"lineNumber": err[@"lineNumber"] ?: @0
}];
}
userInfo = @{
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
@"stack": fakeStack,
};
} else {
userInfo = @{NSLocalizedDescriptionKey: rawText};
}
error = [NSError errorWithDomain:@"JSServer"
code:[(NSHTTPURLResponse *)response statusCode]
userInfo:userInfo];
// Handle HTTP errors
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
NSDictionary *userInfo;
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
if ([errorDetails isKindOfClass:[NSDictionary class]] &&
[errorDetails[@"errors"] isKindOfClass:[NSArray class]]) {
NSMutableArray *fakeStack = [[NSMutableArray alloc] init];
for (NSDictionary *err in errorDetails[@"errors"]) {
[fakeStack addObject: @{
@"methodName": err[@"description"] ?: @"",
@"file": err[@"filename"] ?: @"",
@"lineNumber": err[@"lineNumber"] ?: @0
}];
}
userInfo = @{
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
@"stack": fakeStack,
};
} else {
userInfo = @{NSLocalizedDescriptionKey: rawText};
}
error = [NSError errorWithDomain:@"JSServer"
code:[(NSHTTPURLResponse *)response statusCode]
userInfo:userInfo];
onComplete(error);
return;
}
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = scriptURL;
sourceCodeModule.scriptText = rawText;
onComplete(error);
return;
}
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = scriptURL;
sourceCodeModule.scriptText = rawText;
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
dispatch_async(dispatch_get_main_queue(), ^{
onComplete(scriptError);
});
}];
}];
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
dispatch_async(dispatch_get_main_queue(), ^{
onComplete(scriptError);
});
}];
}];
[task resume];
}

View File

@ -23,16 +23,6 @@
#import "RCTWebViewExecutor.h"
#import "UIView+React.h"
/**
* HACK(t6568049) This should be removed soon, hiding to prevent people from
* relying on it
*/
@interface RCTBridge (RCTRootView)
- (void)setJavaScriptExecutor:(id<RCTJavaScriptExecutor>)executor;
@end
@interface RCTUIManager (RCTRootView)
- (NSNumber *)allocateRootTag;
@ -120,11 +110,11 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
dispatch_async(dispatch_get_main_queue(), ^{
/**
* Every root view that is created must have a unique react tag.
* Every root view that is created must have a unique React tag.
* Numbering of these tags goes from 1, 11, 21, 31, etc
*
* NOTE: Since the bridge persists, the RootViews might be reused, so now
* the react tag is assigned every time we load new content.
* the React tag is assigned every time we load new content.
*/
[_contentView removeFromSuperview];
_contentView = [[UIView alloc] initWithFrame:self.bounds];

Some files were not shown because too many files have changed in this diff Show More