From 5006eca4a84e0d57a1f57e611081338cbc2fa2dd Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 15 Jul 2015 08:23:48 -0700 Subject: [PATCH 01/17] =?UTF-8?q?[TextInput]=20Remove=20focus=20on=20TextI?= =?UTF-8?q?nput=20if=20property=20`editable`=20is=20=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Clicking on a TextField with editable set to false still would trigger a keyboard event. In this case that the TextField was a multiline, it would scroll you down to the bottom. Closes https://github.com/facebook/react-native/pull/1855 Github Author: Christopher --- Libraries/Components/TextInput/TextInput.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 141cefab0..cc1b00b41 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -537,7 +537,9 @@ var TextInput = React.createClass({ }, _onPress: function(event: Event) { - this.focus(); + if (this.props.editable || this.props.editable === undefined) { + this.focus(); + } }, _onChange: function(event: Event) { From 09236ccbe74775359dff14404214b7082e34cc8e Mon Sep 17 00:00:00 2001 From: Mr Speaker Date: Wed, 15 Jul 2015 10:05:13 -0700 Subject: [PATCH 02/17] Allow horizontal ListView. Rename height -> size Summary: Infinite scrolling in horizontal ListViews. Rather than just using height and Y offset to determine when to load more rows, it checks `props.horizontal` and switches between width/height and offset X/Y accordingly. This changed required some renaming. However, the only change external to `ListView.js` is exporting `contentSize` instead of `contentHeight` from the `getMetrics()` function. (This is not part of the API, but is used "for perf investigations or analytics" and isn't reference in the repo). I believe this change works as expected (and the xcode tests pass) though it's possible that there may more complexity in this issue that I have overlooked. Closes https://github.com/facebook/react-native/pull/1786 Github Author: Mr Speaker --- .../CustomComponents/ListView/ListView.js | 60 +++++++++++-------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index 16faeb100..93ba01f34 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -209,7 +209,7 @@ var ListView = React.createClass({ */ getMetrics: function() { return { - contentHeight: this.scrollProperties.contentHeight, + contentLength: this.scrollProperties.contentLength, totalRows: this.props.dataSource.getRowCount(), renderedRows: this.state.curRenderedRowsCount, visibleRows: Object.keys(this._visibleRows).length, @@ -255,9 +255,9 @@ var ListView = React.createClass({ componentWillMount: function() { // this data should never trigger a render pass, so don't put in state this.scrollProperties = { - visibleHeight: null, - contentHeight: null, - offsetY: 0 + visibleLength: null, + contentLength: null, + offset: 0 }; this._childFrames = []; this._visibleRows = {}; @@ -409,12 +409,12 @@ var ListView = React.createClass({ scrollComponent.getInnerViewNode(), React.findNodeHandle(scrollComponent), logError, - this._setScrollContentHeight + this._setScrollContentLength ); RCTUIManager.measureLayoutRelativeToParent( React.findNodeHandle(scrollComponent), logError, - this._setScrollVisibleHeight + this._setScrollVisibleLength ); // RCTScrollViewManager.calculateChildFrames is not available on @@ -426,12 +426,14 @@ var ListView = React.createClass({ ); }, - _setScrollContentHeight: function(left, top, width, height) { - this.scrollProperties.contentHeight = height; + _setScrollContentLength: function(left, top, width, height) { + this.scrollProperties.contentLength = !this.props.horizontal ? + height : width; }, - _setScrollVisibleHeight: function(left, top, width, height) { - this.scrollProperties.visibleHeight = height; + _setScrollVisibleLength: function(left, top, width, height) { + this.scrollProperties.visibleLength = !this.props.horizontal ? + height : width; this._updateVisibleRows(); this._renderMoreRowsIfNeeded(); }, @@ -441,8 +443,8 @@ var ListView = React.createClass({ }, _renderMoreRowsIfNeeded: function() { - if (this.scrollProperties.contentHeight === null || - this.scrollProperties.visibleHeight === null || + if (this.scrollProperties.contentLength === null || + this.scrollProperties.visibleLength === null || this.state.curRenderedRowsCount === this.props.dataSource.getRowCount()) { return; } @@ -472,9 +474,9 @@ var ListView = React.createClass({ }, _getDistanceFromEnd: function(scrollProperties) { - return scrollProperties.contentHeight - - scrollProperties.visibleHeight - - scrollProperties.offsetY; + return scrollProperties.contentLength - + scrollProperties.visibleLength - + scrollProperties.offset; }, _updateVisibleRows: function(updatedFrames) { @@ -486,9 +488,10 @@ var ListView = React.createClass({ this._childFrames[newFrame.index] = merge(newFrame); }); } + var isVertical = !this.props.horizontal; var dataSource = this.props.dataSource; - var visibleTop = this.scrollProperties.offsetY; - var visibleBottom = visibleTop + this.scrollProperties.visibleHeight; + var visibleMin = this.scrollProperties.offset; + var visibleMax = visibleMin + this.scrollProperties.visibleLength; var allRowIDs = dataSource.rowIdentities; var header = this.props.renderHeader && this.props.renderHeader(); @@ -516,9 +519,9 @@ var ListView = React.createClass({ break; } var rowVisible = visibleSection[rowID]; - var top = frame.y; - var bottom = top + frame.height; - if (top > visibleBottom || bottom < visibleTop) { + var min = isVertical ? frame.y : frame.x; + var max = min + (isVertical ? frame.height : frame.width); + if (min > visibleMax || max < visibleMin) { if (rowVisible) { visibilityChanged = true; delete visibleSection[rowID]; @@ -546,16 +549,23 @@ var ListView = React.createClass({ }, _onScroll: function(e) { - this.scrollProperties.visibleHeight = e.nativeEvent.layoutMeasurement.height; - this.scrollProperties.contentHeight = e.nativeEvent.contentSize.height; - this.scrollProperties.offsetY = e.nativeEvent.contentOffset.y; + var isVertical = !this.props.horizontal; + this.scrollProperties.visibleLength = e.nativeEvent.layoutMeasurement[ + isVertical ? 'height' : 'width' + ]; + this.scrollProperties.contentLength = e.nativeEvent.contentSize[ + isVertical ? 'height' : 'width' + ]; + this.scrollProperties.offset = e.nativeEvent.contentOffset[ + isVertical ? 'y' : 'x' + ]; this._updateVisibleRows(e.nativeEvent.updatedChildFrames); var nearEnd = this._getDistanceFromEnd(this.scrollProperties) < this.props.onEndReachedThreshold; if (nearEnd && this.props.onEndReached && - this.scrollProperties.contentHeight !== this._sentEndForContentHeight && + this.scrollProperties.contentLength !== this._sentEndForContentLength && this.state.curRenderedRowsCount === this.props.dataSource.getRowCount()) { - this._sentEndForContentHeight = this.scrollProperties.contentHeight; + this._sentEndForContentLength = this.scrollProperties.contentLength; this.props.onEndReached(e); } else { this._renderMoreRowsIfNeeded(); From 72694a722e047fe62034e695b133a9ecd38f9160 Mon Sep 17 00:00:00 2001 From: John Ku Date: Wed, 15 Jul 2015 18:49:37 -0700 Subject: [PATCH 03/17] Include jestSupport for npm package Summary: https://github.com/facebook/react-native/pull/1639 was closed for unknown reason. This will help unit test with Jest without manually copying them into node_modules/react-native. Closes https://github.com/facebook/react-native/pull/1848 Github Author: John Ku --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 7e5a98aba..0d119c5d2 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "init.sh", "LICENSE", "PATENTS", - "README.md" + "README.md", + "jestSupport" ], "scripts": { "test": "jest", From 9f94dd457aa29302721db38672555d0df07ae65d Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Thu, 16 Jul 2015 01:57:46 -0700 Subject: [PATCH 04/17] Camera node for scene hierarchy --- Libraries/Utilities/MatrixMath.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Libraries/Utilities/MatrixMath.js b/Libraries/Utilities/MatrixMath.js index c6a26ea82..6103e1d1e 100755 --- a/Libraries/Utilities/MatrixMath.js +++ b/Libraries/Utilities/MatrixMath.js @@ -31,6 +31,24 @@ var MatrixMath = { ]; }, + createFrustum: function(left, right, bottom, top, near, far) { + var r_width = 1 / (right - left); + var r_height = 1 / (top - bottom); + var r_depth = 1 / (near - far); + var x = 2 * (near * r_width); + var y = 2 * (near * r_height); + var A = (right + left) * r_width; + var B = (top + bottom) * r_height; + var C = (far + near) * r_depth; + var D = 2 * (far * near * r_depth); + return [ + x, 0, 0, 0, + 0, y, 0, 0, + A, B, C,-1, + 0, 0, D, 0, + ]; + }, + createTranslate2d: function(x, y) { var mat = MatrixMath.createIdentityMatrix(); MatrixMath.reuseTranslate2dCommand(mat, x, y); From 2cb634bb0b564a5e4ddadd259df560a53c91e455 Mon Sep 17 00:00:00 2001 From: Mr Speaker Date: Thu, 16 Jul 2015 04:18:12 -0700 Subject: [PATCH 05/17] Nav ios hide hairline Summary: For the current project I am working on I needed to add a boolean option (`shadowHidden`) to remove the 1px hairline shadow from the NavigationIOS component: `` By default, or with `shadowHidden={false}` it looks like this: ![withhairline](https://cloud.githubusercontent.com/assets/129330/8145401/d2704956-11d4-11e5-86e8-a75435b68480.png) Setting the shadow hidden with `shadowHidden={true}` looks like this: ![nohairline2](https://cloud.githubusercontent.com/assets/129330/8145405/148ed56e-11d5-11e5-85d6-f8cd3453d5ac.png) The code it uses to do the actual hiding is... a bit of a hack (*I* think), but it's the only way currently to do it, and is the way they do it in the iPhone calendar app: iterating through the subviews and removing the shadow image. This removes the shadow *and* keeps the translucent blurry effect with a ScrollView (see this [SO discussion](http://stackoverflow.com/questions/18160173/how-to-remove-uinavigationbar-inner Closes https://github.com/facebook/react-native/pull/1615 Github Author: Mr Speaker --- .../Components/Navigation/NavigatorIOS.ios.js | 7 ++++ React/Views/RCTNavItem.h | 1 + React/Views/RCTNavItemManager.m | 1 + React/Views/RCTWrapperViewController.m | 33 +++++++++++++------ 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js index 053ab0ee3..5d47b4df6 100644 --- a/Libraries/Components/Navigation/NavigatorIOS.ios.js +++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js @@ -60,6 +60,7 @@ var RCTNavigatorItem = createReactNativeComponentClass({ tintColor: true, translucent: true, navigationBarHidden: true, + shadowHidden: true, titleTextColor: true, style: true, }, @@ -281,6 +282,11 @@ var NavigatorIOS = React.createClass({ */ navigationBarHidden: PropTypes.bool, + /** + * A Boolean value that indicates whether to hide the 1px hairline shadow + */ + shadowHidden: PropTypes.bool, + /** * The default wrapper style for components in the navigator. * A common use case is to set the backgroundColor for every page @@ -640,6 +646,7 @@ var NavigatorIOS = React.createClass({ rightButtonTitle={route.rightButtonTitle} onNavRightButtonTap={route.onRightButtonPress} navigationBarHidden={this.props.navigationBarHidden} + shadowHidden={this.props.shadowHidden} tintColor={this.props.tintColor} barTintColor={this.props.barTintColor} translucent={this.props.translucent !== false} diff --git a/React/Views/RCTNavItem.h b/React/Views/RCTNavItem.h index 16c1e7f5d..9b75673c3 100644 --- a/React/Views/RCTNavItem.h +++ b/React/Views/RCTNavItem.h @@ -19,6 +19,7 @@ @property (nonatomic, strong) UIImage *backButtonIcon; @property (nonatomic, copy) NSString *backButtonTitle; @property (nonatomic, assign) BOOL navigationBarHidden; +@property (nonatomic, assign) BOOL shadowHidden; @property (nonatomic, strong) UIColor *tintColor; @property (nonatomic, strong) UIColor *barTintColor; @property (nonatomic, strong) UIColor *titleTextColor; diff --git a/React/Views/RCTNavItemManager.m b/React/Views/RCTNavItemManager.m index dab6ee049..8350af2e9 100644 --- a/React/Views/RCTNavItemManager.m +++ b/React/Views/RCTNavItemManager.m @@ -22,6 +22,7 @@ RCT_EXPORT_MODULE() } RCT_EXPORT_VIEW_PROPERTY(navigationBarHidden, BOOL) +RCT_EXPORT_VIEW_PROPERTY(shadowHidden, BOOL) RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor) RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor) RCT_EXPORT_VIEW_PROPERTY(translucent, BOOL) diff --git a/React/Views/RCTWrapperViewController.m b/React/Views/RCTWrapperViewController.m index c1893353b..b9fd8ec3d 100644 --- a/React/Views/RCTWrapperViewController.m +++ b/React/Views/RCTWrapperViewController.m @@ -63,6 +63,20 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) _currentBottomLayoutGuide = self.bottomLayoutGuide; } +static UIView *RCTFindNavBarShadowViewInView(UIView *view) +{ + if ([view isKindOfClass:[UIImageView class]] && view.bounds.size.height <= 1) { + return view; + } + for (UIView *subview in view.subviews) { + UIView *shadowView = RCTFindNavBarShadowViewInView(subview); + if (shadowView) { + return shadowView; + } + } + return nil; +} + - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; @@ -71,20 +85,18 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) if ([self.parentViewController isKindOfClass:[UINavigationController class]]) { [self.navigationController - setNavigationBarHidden:_navItem.navigationBarHidden - animated:animated]; - - if (!_navItem) { - return; - } + setNavigationBarHidden:_navItem.navigationBarHidden + animated:animated]; UINavigationBar *bar = self.navigationController.navigationBar; bar.barTintColor = _navItem.barTintColor; bar.tintColor = _navItem.tintColor; bar.translucent = _navItem.translucent; - if (_navItem.titleTextColor) { - [bar setTitleTextAttributes:@{NSForegroundColorAttributeName : _navItem.titleTextColor}]; - } + bar.titleTextAttributes = _navItem.titleTextColor ? @{ + NSForegroundColorAttributeName: _navItem.titleTextColor + } : nil; + + RCTFindNavBarShadowViewInView(bar).hidden = _navItem.shadowHidden; UINavigationItem *item = self.navigationItem; item.title = _navItem.title; @@ -129,7 +141,8 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) // finishes, be it a swipe to go back or a standard tap on the back button [super didMoveToParentViewController:parent]; if (parent == nil || [parent isKindOfClass:[UINavigationController class]]) { - [self.navigationListener wrapperViewController:self didMoveToNavigationController:(UINavigationController *)parent]; + [self.navigationListener wrapperViewController:self + didMoveToNavigationController:(UINavigationController *)parent]; } } From b362072c83b5fe883e913e5752735fc6e561219b Mon Sep 17 00:00:00 2001 From: James Ide Date: Thu, 16 Jul 2015 03:51:54 -0700 Subject: [PATCH 06/17] [Rendering] Set allowsEdgeAntialiasing for transformed views Summary: By default, the edges of rotated layers on iOS have jagged edges because they are not antialiased. Setting `allowsEdgeAntialiasing` makes them look a lot nicer by smoothing out the jaggies. This is particularly important for UIs like Tinder cards, for example. Closes https://github.com/facebook/react-native/pull/1999 Github Author: James Ide --- React/Views/RCTViewManager.m | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index 9be9087f6..1c6c2f17d 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -102,8 +102,13 @@ RCT_REMAP_VIEW_PROPERTY(shadowColor, layer.shadowColor, CGColor); RCT_REMAP_VIEW_PROPERTY(shadowOffset, layer.shadowOffset, CGSize); RCT_REMAP_VIEW_PROPERTY(shadowOpacity, layer.shadowOpacity, float) RCT_REMAP_VIEW_PROPERTY(shadowRadius, layer.shadowRadius, CGFloat) -RCT_REMAP_VIEW_PROPERTY(transformMatrix, layer.transform, CATransform3D) RCT_REMAP_VIEW_PROPERTY(overflow, clipsToBounds, css_clip_t) +RCT_CUSTOM_VIEW_PROPERTY(transformMatrix, CATransform3D, RCTView) +{ + view.layer.transform = json ? [RCTConvert CATransform3D:json] : defaultView.layer.transform; + // TODO: Improve this by enabling edge antialiasing only for transforms with rotation or skewing + view.layer.allowsEdgeAntialiasing = !CATransform3DIsIdentity(view.layer.transform); +} RCT_CUSTOM_VIEW_PROPERTY(pointerEvents, RCTPointerEvents, RCTView) { if ([view respondsToSelector:@selector(setPointerEvents:)]) { From f74efc13ede39b114b0fdb4914c2e65071273e77 Mon Sep 17 00:00:00 2001 From: Adam Farhi Date: Thu, 16 Jul 2015 05:44:56 -0700 Subject: [PATCH 07/17] Add ListView Grid Layout Example to UIExplorer Examples Summary: On @brentvatne suggestion: https://github.com/facebook/react-native/issues/1276#issuecomment-112161358 I imported my Grid Layout example (https://github.com/yelled3/react-native-grid-example) into UIExplorer. ![demo-grid](https://cloud.githubusercontent.com/assets/818124/8229685/c4325dd4-15c1-11e5-8817-cdf270ab1003.gif) Closes https://github.com/facebook/react-native/pull/1668 Github Author: Adam Farhi --- .../UIExplorer/ListViewGridLayoutExample.js | 149 ++++++++++++++++++ Examples/UIExplorer/UIExplorerList.js | 1 + 2 files changed, 150 insertions(+) create mode 100644 Examples/UIExplorer/ListViewGridLayoutExample.js diff --git a/Examples/UIExplorer/ListViewGridLayoutExample.js b/Examples/UIExplorer/ListViewGridLayoutExample.js new file mode 100644 index 000000000..879b24387 --- /dev/null +++ b/Examples/UIExplorer/ListViewGridLayoutExample.js @@ -0,0 +1,149 @@ +/** + * 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 { + Image, + ListView, + TouchableHighlight, + StyleSheet, + Text, + View, +} = React; + +var THUMB_URLS = [ + 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851549_767334479959628_274486868_n.png', + 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851561_767334496626293_1958532586_n.png', + 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851579_767334503292959_179092627_n.png', + 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851589_767334513292958_1747022277_n.png', + 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851563_767334559959620_1193692107_n.png', + 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851593_767334566626286_1953955109_n.png', + 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851591_767334523292957_797560749_n.png', + 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851567_767334529959623_843148472_n.png', + 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851548_767334489959627_794462220_n.png', + 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851575_767334539959622_441598241_n.png', + 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851573_767334549959621_534583464_n.png', + 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851583_767334573292952_1519550680_n.png', +]; + +var ListViewGridLayoutExample = React.createClass({ + + statics: { + title: ' - Grid Layout', + description: 'Flexbox grid layout.' + }, + + getInitialState: function() { + var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); + return { + dataSource: ds.cloneWithRows(this._genRows({})), + }; + }, + + _pressData: ({}: {[key: number]: boolean}), + + componentWillMount: function() { + this._pressData = {}; + }, + + render: function() { + return ( + // ListView wraps ScrollView and so takes on its properties. + // With that in mind you can use the ScrollView's contentContainerStyle prop to style the items. + + ); + }, + + _renderRow: function(rowData: string, sectionID: number, rowID: number) { + var rowHash = Math.abs(hashCode(rowData)); + var imgSource = { + uri: THUMB_URLS[rowHash % THUMB_URLS.length], + }; + return ( + this._pressRow(rowID)} underlayColor="transparent"> + + + + + {rowData} + + + + + ); + }, + + _genRows: function(pressData: {[key: number]: boolean}): Array { + var dataBlob = []; + for (var ii = 0; ii < 100; ii++) { + var pressedText = pressData[ii] ? ' (X)' : ''; + dataBlob.push('Cell ' + ii + pressedText); + } + return dataBlob; + }, + + _pressRow: function(rowID: number) { + this._pressData[rowID] = !this._pressData[rowID]; + this.setState({dataSource: this.state.dataSource.cloneWithRows( + this._genRows(this._pressData) + )}); + }, +}); + +/* eslint no-bitwise: 0 */ +var hashCode = function(str) { + var hash = 15; + for (var ii = str.length - 1; ii >= 0; ii--) { + hash = ((hash << 5) - hash) + str.charCodeAt(ii); + } + return hash; +}; + +var styles = StyleSheet.create({ + list: { + justifyContent: 'space-around', + flexDirection: 'row', + flexWrap: 'wrap' + }, + row: { + justifyContent: 'center', + padding: 5, + margin: 3, + width: 100, + height: 100, + backgroundColor: '#F6F6F6', + alignItems: 'center', + borderWidth: 1, + borderRadius: 5, + borderColor: '#CCC' + }, + thumb: { + width: 64, + height: 64 + }, + text: { + flex: 1, + marginTop: 5, + fontWeight: 'bold' + }, +}); + +module.exports = ListViewGridLayoutExample; diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index 20edd13b0..c3fc0492a 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -40,6 +40,7 @@ var COMMON_COMPONENTS = [ require('./ImageExample'), require('./LayoutEventsExample'), require('./ListViewExample'), + require('./ListViewGridLayoutExample'), require('./ListViewPagingExample'), require('./MapViewExample'), require('./Navigator/NavigatorExample'), From 36f76e58939d211eab4edbaf149a70bb0b31fd3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Sagnes?= Date: Thu, 16 Jul 2015 09:23:23 -0700 Subject: [PATCH 08/17] Improve the performance of React Native tests --- .../UIExplorerUnitTests/RCTAllocationTests.m | 31 ++++++------------- .../RCTContextExecutorTests.m | 7 ++++- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m index 14cc34484..8fc629fbe 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m @@ -19,17 +19,15 @@ #import "RCTContextExecutor.h" #import "RCTRootView.h" -#define RUN_RUNLOOP_WHILE(CONDITION, TIMEOUT) \ +#define RUN_RUNLOOP_WHILE(CONDITION) \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wshadow\"") \ -NSDate *timeout = [[NSDate date] dateByAddingTimeInterval:TIMEOUT]; \ +NSDate *timeout = [[NSDate date] dateByAddingTimeInterval:0.1]; \ while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \ [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; \ } \ _Pragma("clang diagnostic pop") -#define DEFAULT_TIMEOUT 2 - @interface RCTBridge (RCTAllocationTests) @property (nonatomic, weak) RCTBridge *batchedBridge; @@ -83,7 +81,6 @@ RCT_EXPORT_MODULE(); (void)view; } - sleep(DEFAULT_TIMEOUT); XCTAssertNil(weakBridge, @"RCTBridge should have been deallocated"); } @@ -104,8 +101,7 @@ RCT_EXPORT_MODULE(); * Sleep on the main thread to allow js thread deallocations then run the runloop * to allow the module to be deallocated on the main thread */ - sleep(1); - RUN_RUNLOOP_WHILE(module.isValid, 1) + RUN_RUNLOOP_WHILE(module.isValid) XCTAssertFalse(module.isValid, @"AllocationTestModule should have been invalidated by the bridge"); } @@ -124,12 +120,7 @@ RCT_EXPORT_MODULE(); (void)bridge; } - /** - * Sleep on the main thread to allow js thread deallocations then run the runloop - * to allow the module to be deallocated on the main thread - */ - sleep(1); - RUN_RUNLOOP_WHILE(weakModule, 1) + RUN_RUNLOOP_WHILE(weakModule) XCTAssertNil(weakModule, @"AllocationTestModule should have been deallocated"); } @@ -145,8 +136,7 @@ RCT_EXPORT_MODULE(); (void)bridge; } - RUN_RUNLOOP_WHILE(weakExecutor, 1); - sleep(1); + RUN_RUNLOOP_WHILE(weakExecutor); XCTAssertNil(weakExecutor, @"JavaScriptExecutor should have been released"); } @@ -158,13 +148,12 @@ RCT_EXPORT_MODULE(); moduleProvider:nil launchOptions:nil]; id executor = [bridge.batchedBridge valueForKey:@"javaScriptExecutor"]; - RUN_RUNLOOP_WHILE(!(weakContext = [executor valueForKey:@"context"]), DEFAULT_TIMEOUT); + RUN_RUNLOOP_WHILE(!(weakContext = [executor valueForKey:@"context"])); XCTAssertNotNil(weakContext, @"RCTJavaScriptContext should have been created"); (void)bridge; } - RUN_RUNLOOP_WHILE(weakContext, 1); - sleep(1); + RUN_RUNLOOP_WHILE(weakContext); XCTAssertNil(weakContext, @"RCTJavaScriptContext should have been deallocated"); } @@ -176,12 +165,11 @@ RCT_EXPORT_MODULE(); __weak id rootContentView; @autoreleasepool { RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@""]; - RUN_RUNLOOP_WHILE(!(rootContentView = [rootView valueForKey:@"contentView"]), DEFAULT_TIMEOUT) + RUN_RUNLOOP_WHILE(!(rootContentView = [rootView valueForKey:@"contentView"])) XCTAssertTrue([rootContentView isValid], @"RCTContentView should be valid"); (void)rootView; } - sleep(DEFAULT_TIMEOUT); XCTAssertFalse([rootContentView isValid], @"RCTContentView should have been invalidated"); } @@ -196,8 +184,7 @@ RCT_EXPORT_MODULE(); [bridge reload]; } - // Use RUN_RUNLOOP_WHILE because `batchedBridge` deallocates on the main thread. - RUN_RUNLOOP_WHILE(batchedBridge != nil, DEFAULT_TIMEOUT) + RUN_RUNLOOP_WHILE(batchedBridge != nil) XCTAssertNotNil(bridge, @"RCTBridge should not have been deallocated"); XCTAssertNil(batchedBridge, @"RCTBatchedBridge should have been deallocated"); diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m index 18b4118ba..f7db2d46f 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m @@ -19,6 +19,7 @@ #import "RCTContextExecutor.h" #import "RCTUtils.h" +#define RUN_PERF_TESTS 0 @interface RCTContextExecutorTests : XCTestCase @@ -48,6 +49,8 @@ [_executor invalidate]; } +#if RUN_PERF_TESTS + static uint64_t _get_time_nanoseconds(void) { static struct mach_timebase_info tb_info = {0, 0}; @@ -91,7 +94,7 @@ static uint64_t _get_time_nanoseconds(void) JSContextGroupRelease(group); } -- (void)MANUALLY_testJavaScriptCallSpeed +- (void)testJavaScriptCallSpeed { /** * Since we almost don't change the RCTContextExecutor logic, and this test is @@ -200,4 +203,6 @@ static uint64_t _get_time_nanoseconds(void) } } +#endif + @end From 81ad713f5f4353ccb803d583e08bf0520d502e93 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 16 Jul 2015 09:34:28 -0700 Subject: [PATCH 09/17] Added Gzip support Summary: Added Gzip function to RCTUtils. This uses dlopen to load the zlib library at runtime so there's no need to link it into your project. The main reason for this feature is to support gzipping of HTTP request bodies. Now, if you add 'Content-Encoding:gzip' to your request headers when using XMLHttpRequest, your request body will be automatically gzipped on the native side before sending. (Note: Gzip decoding of *response* bodies is handled automatically by iOS, and was already available). --- Libraries/Network/RCTNetworking.m | 9 +++++- React/Base/RCTUtils.h | 7 ++-- React/Base/RCTUtils.m | 53 ++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m index b6fb100d7..d86b00950 100644 --- a/Libraries/Network/RCTNetworking.m +++ b/Libraries/Network/RCTNetworking.m @@ -249,8 +249,15 @@ RCT_EXPORT_MODULE() request.HTTPBody = result[@"body"]; NSString *contentType = result[@"contentType"]; if (contentType) { - [request setValue:contentType forHTTPHeaderField:@"content-type"]; + [request setValue:contentType forHTTPHeaderField:@"Content-Type"]; } + + // Gzip the request body + if ([request.allHTTPHeaderFields[@"Content-Encoding"] isEqualToString:@"gzip"]) { + request.HTTPBody = RCTGzipData(request.HTTPBody, -1 /* default */); + [request setValue:[@(request.HTTPBody.length) description] forHTTPHeaderField:@"Content-Length"]; + } + [self sendRequest:request incrementalUpdates:incrementalUpdates responseSender:responseSender]; diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index e21a07222..23926aa71 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -15,7 +15,7 @@ #import "RCTAssert.h" #import "RCTDefines.h" -// Utility functions for JSON object <-> string serialization/deserialization +// JSON serialization/deserialization RCT_EXTERN NSString *RCTJSONStringify(id jsonObject, NSError **error); RCT_EXTERN id RCTJSONParse(NSString *jsonString, NSError **error); RCT_EXTERN id RCTJSONParseMutable(NSString *jsonString, NSError **error); @@ -24,7 +24,7 @@ RCT_EXTERN id RCTJSONParseWithOptions(NSString *jsonString, NSError **error, NSJ // Strip non JSON-safe values from an object graph RCT_EXTERN id RCTJSONClean(id object); -// Get MD5 hash of a string (TODO: currently unused. Remove?) +// Get MD5 hash of a string RCT_EXTERN NSString *RCTMD5Hash(NSString *string); // Get screen metrics in a thread-safe way @@ -64,3 +64,6 @@ RCT_EXTERN id RCTNullIfNil(id value); // Convert data to a Base64-encoded data URL RCT_EXTERN NSURL *RCTDataURL(NSString *mimeType, NSData *data); + +// Gzip functionality - compression level in range 0 - 1 (-1 for default) +RCT_EXTERN NSData *RCTGzipData(NSData *data, float level); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 7fc620bc0..d342c3be4 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -10,12 +10,15 @@ #import "RCTUtils.h" #import -#import +#import #import #import +#import +#import + #import "RCTLog.h" NSString *RCTJSONStringify(id jsonObject, NSError **error) @@ -305,3 +308,51 @@ NSURL *RCTDataURL(NSString *mimeType, NSData *data) [data base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]]]; } +static BOOL RCTIsGzippedData(NSData *data) +{ + UInt8 *bytes = (UInt8 *)data.bytes; + return (data.length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b); +} + +NSData *RCTGzipData(NSData *input, float level) +{ + if (input.length == 0 || RCTIsGzippedData(input)) { + return input; + } + + void *libz = dlopen("libz.dylib", RTLD_NOW); + int (*deflateInit2_)(z_streamp, int, int, int, int, int, const char *, int) = dlsym(libz, "deflateInit2_"); + int (*deflate)(z_streamp, int) = dlsym(libz, "deflate"); + int (*deflateEnd)(z_streamp) = dlsym(libz, "deflateEnd"); + + z_stream stream; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + stream.avail_in = (uint)input.length; + stream.next_in = (Bytef *)input.bytes; + stream.total_out = 0; + stream.avail_out = 0; + + static const NSUInteger RCTGZipChunkSize = 16384; + + NSMutableData *output = nil; + int compression = (level < 0.0f)? Z_DEFAULT_COMPRESSION: (int)(roundf(level * 9)); + if (deflateInit2(&stream, compression, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) == Z_OK) { + output = [NSMutableData dataWithLength:RCTGZipChunkSize]; + while (stream.avail_out == 0) { + if (stream.total_out >= output.length) { + output.length += RCTGZipChunkSize; + } + stream.next_out = (uint8_t *)output.mutableBytes + stream.total_out; + stream.avail_out = (uInt)(output.length - stream.total_out); + deflate(&stream, Z_FINISH); + } + deflateEnd(&stream); + output.length = stream.total_out; + } + + dlclose(libz); + + return output; +} From fa4c570d33de1f7c0ab17b5da4b3b23a73d63d47 Mon Sep 17 00:00:00 2001 From: Sean Powell Date: Thu, 16 Jul 2015 14:16:50 -0700 Subject: [PATCH 10/17] Support debugger reconnection when the packager goes down. Summary: This should resolve the issue highlighted in #1709 Closes https://github.com/facebook/react-native/pull/1992 Github Author: Sean Powell --- packager/debugger.html | 67 +++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/packager/debugger.html b/packager/debugger.html index 0d0084559..c9a816023 100644 --- a/packager/debugger.html +++ b/packager/debugger.html @@ -27,6 +27,11 @@ window.onbeforeunload = function() { } }; +// Alias native implementations needed by the debugger before platform-specific +// implementations are loaded into the global namespace +var debuggerSetTimeout = window.setTimeout; +var DebuggerWebSocket = window.WebSocket; + function setStatus(status) { document.getElementById('status').innerHTML = status; } @@ -58,35 +63,43 @@ var messageHandlers = { sendReply(JSON.stringify(returnValue)); } } +}; + +function connectToDebuggerProxy() { + var ws = new DebuggerWebSocket('ws://' + window.location.host + '/debugger-proxy'); + + ws.onopen = function() { + if (sessionID) { + setStatus('Debugger session #' + sessionID + ' active'); + ws.send(JSON.stringify({replyID: parseInt(sessionID, 10)})); + } else { + setStatus('Waiting, press ⌘R in simulator to reload and connect'); + } + }; + + ws.onmessage = function(message) { + var object = JSON.parse(message.data); + var sendReply = function(result) { + ws.send(JSON.stringify({replyID: object.id, result: result})); + }; + var handler = messageHandlers[object.method]; + if (handler) { + handler(object, sendReply); + } else { + console.warn('Unknown method: ' + object.method); + } + }; + + ws.onclose = function() { + setStatus('Disconnected from proxy. Attempting reconnection. Is node server running?'); + + sessionID = null; + window.localStorage.removeItem('sessionID'); + debuggerSetTimeout(connectToDebuggerProxy, 100); + }; } -var ws = new WebSocket('ws://' + window.location.host + '/debugger-proxy'); - -ws.onopen = function() { - if (sessionID) { - setStatus('Debugger session #' + sessionID + ' active'); - ws.send(JSON.stringify({replyID: parseInt(sessionID, 10)})); - } else { - setStatus('Waiting, press ⌘R in simulator to reload and connect'); - } -} - -ws.onmessage = function(message) { - var object = JSON.parse(message.data); - var sendReply = function(result) { - ws.send(JSON.stringify({replyID: object.id, result: result})); - } - var handler = messageHandlers[object.method]; - if (handler) { - handler(object, sendReply); - } else { - console.warn('Unknown method: ' + object.method); - } -} - -ws.onclose = function() { - setStatus('Disconnected from proxy. Is node server running?'); -} +connectToDebuggerProxy(); function loadScript(src, callback) { var script = document.createElement('script'); From fca16fbe40c496abed19ac1909e8a1e653f2eeb5 Mon Sep 17 00:00:00 2001 From: Hedger Wang Date: Thu, 16 Jul 2015 13:26:28 -0700 Subject: [PATCH 11/17] [ReactNative] unbreak navigation due to onItemRef removal --- Libraries/CustomComponents/Navigator/Navigator.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index 573223771..5121b108e 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -45,6 +45,7 @@ var View = require('View'); var clamp = require('clamp'); var flattenStyle = require('flattenStyle'); +var guid = require('guid'); var invariant = require('invariant'); var rebound = require('rebound'); @@ -1012,14 +1013,14 @@ var Navigator = React.createClass({ } }, - _renderScene: function(route, i) { + _renderScene: function(route, i, key) { var disabledSceneStyle = null; if (i !== this.state.presentedIndex) { disabledSceneStyle = styles.disabledScene; } return ( { return (this.state.transitionFromIndex != null) || (this.state.transitionFromIndex != null); @@ -1052,7 +1053,7 @@ var Navigator = React.createClass({ index !== this.state.presentedIndex) { renderedScene = this._renderedSceneMap.get(route); } else { - renderedScene = this._renderScene(route, index); + renderedScene = this._renderScene(route, index, guid()); } newRenderedSceneMap.set(route, renderedScene); return renderedScene; From ab9a87c33a1a282a0eed30e180c6560ed1dc03e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Thu, 16 Jul 2015 14:08:25 -0700 Subject: [PATCH 12/17] [cg] Perf logging --- Libraries/Utilities/PerformanceLogger.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Libraries/Utilities/PerformanceLogger.js b/Libraries/Utilities/PerformanceLogger.js index c05c4b7c2..c7f0e316c 100644 --- a/Libraries/Utilities/PerformanceLogger.js +++ b/Libraries/Utilities/PerformanceLogger.js @@ -24,7 +24,8 @@ var PerformanceLogger = { if (timespans[key]) { if (__DEV__) { console.log( - 'PerformanceLogger: Attempting to add a timespan that already exists' + 'PerformanceLogger: Attempting to add a timespan that already exists ', + key ); } return; @@ -40,7 +41,8 @@ var PerformanceLogger = { if (timespans[key]) { if (__DEV__) { console.log( - 'PerformanceLogger: Attempting to start a timespan that already exists' + 'PerformanceLogger: Attempting to start a timespan that already exists ', + key, ); } return; @@ -56,7 +58,8 @@ var PerformanceLogger = { if (!timespans[key] || !timespans[key].startTime) { if (__DEV__) { console.log( - 'PerformanceLogger: Attempting to end a timespan that has not started' + 'PerformanceLogger: Attempting to end a timespan that has not started ', + key, ); } return; @@ -75,6 +78,10 @@ var PerformanceLogger = { return timespans; }, + hasTimespan(key) { + return !!timespans[key]; + }, + logTimespans() { for (var key in timespans) { console.log(key + ': ' + timespans[key].totalTime + 'ms'); From 4b82673484a02f570f40e77d77748b887e0d41a1 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Thu, 16 Jul 2015 16:07:19 -0700 Subject: [PATCH 13/17] revert [React Native] Fix scroll view sticky headers --- Libraries/Components/ScrollView/ScrollView.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 2625c20b7..f01cce58a 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -17,7 +17,6 @@ var PointPropType = require('PointPropType'); var RCTScrollView = require('NativeModules').UIManager.RCTScrollView; var RCTScrollViewConsts = RCTScrollView.Constants; var React = require('React'); -var ReactChildren = require('ReactChildren'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var RCTUIManager = require('NativeModules').UIManager; var ScrollResponder = require('ScrollResponder'); @@ -278,24 +277,13 @@ var ScrollView = React.createClass({ } } - var children = this.props.children; - if (this.props.stickyHeaderIndices) { - children = ReactChildren.map(children, (child) => { - if (child) { - return {child}; - } else { - return child; - } - }); - } - var contentContainer = - {children} + {this.props.children} ; var alwaysBounceHorizontal = From 326a66ba077a790211bf8d8842fdb784f1b38aac Mon Sep 17 00:00:00 2001 From: Hedger Wang Date: Thu, 16 Jul 2015 17:21:30 -0700 Subject: [PATCH 14/17] [ReactNative][Navigation]: Add method `preventDefault()` to `NavigationEvent` --- .../Navigator/Navigation/NavigationEvent.js | 113 ++++++++++++++++-- .../__tests__/NavigationEvent-test.js | 71 +++++++++++ 2 files changed, 177 insertions(+), 7 deletions(-) create mode 100644 Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEvent-test.js diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js index b6923b4f2..343e1f3e6 100644 --- a/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js +++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js @@ -1,20 +1,119 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * Copyright (c) 2015, Facebook, Inc. All rights reserved. + * + * Facebook, Inc. (“Facebook”) owns all right, title and interest, including + * all intellectual property and other proprietary rights, in and to the React + * Native CustomComponents software (the “Software”). Subject to your + * compliance with these terms, you are hereby granted a non-exclusive, + * worldwide, royalty-free copyright license to (1) use and copy the Software; + * and (2) reproduce and distribute the Software as part of your own software + * (“Your Software”). Facebook reserves all rights not expressly granted to + * you in this license agreement. + * + * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. + * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR + * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @providesModule NavigationEvent * @flow */ 'use strict'; +var invariant = require('invariant'); + +class NavigationEventPool { + _list: Array; + + constructor() { + this._list = []; + } + + get(type: String, target: Object, data: any): NavigationEvent { + var event; + if (this._list.length > 0) { + event = this._list.pop(); + event.constructor.call(event, type, target, data); + } else { + event = new NavigationEvent(type, target, data); + } + return event; + } + + put(event: NavigationEvent) { + this._list.push(event); + } +} + +var _navigationEventPool = new NavigationEventPool(); + class NavigationEvent { - type: String; - target: Object; - data: any; + _data: any; + _defaultPrevented: boolean; + _disposed: boolean; + _target: ?Object; + _type: ?String; + + static pool(type: String, target: Object, data: any): NavigationEvent { + return _navigationEventPool.get(type, target, data); + } constructor(type: String, target: Object, data: any) { - this.type = type; - this.target = target; - this.data = data; + this._type = type; + this._target = target; + this._data = data; + this._defaultPrevented = false; + this._disposed = false; + } + + /* $FlowFixMe - get/set properties not yet supported */ + get type(): string { + return this._type; + } + + /* $FlowFixMe - get/set properties not yet supported */ + get target(): Object { + return this._target; + } + + /* $FlowFixMe - get/set properties not yet supported */ + get data(): any { + return this._data; + } + + /* $FlowFixMe - get/set properties not yet supported */ + get defaultPrevented(): boolean { + return this._defaultPrevented; + } + + preventDefault(): void { + this._defaultPrevented = true; + } + + /** + * Dispose the event. + * NavigationEvent shall be disposed after being emitted by + * `NavigationEventEmitter`. + */ + dispose(): void { + invariant(!this._disposed, 'NavigationEvent is already disposed'); + this._disposed = true; + + // Clean up. + this._type = null; + this._target = null; + this._data = null; + this._defaultPrevented = false; + + // Put this back to the pool to reuse the instance. + _navigationEventPool.put(this); } } diff --git a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEvent-test.js b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEvent-test.js new file mode 100644 index 000000000..8ae24ddb4 --- /dev/null +++ b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEvent-test.js @@ -0,0 +1,71 @@ + +/** + * Copyright (c) 2015, Facebook, Inc. All rights reserved. + * + * Facebook, Inc. (“Facebook”) owns all right, title and interest, including + * all intellectual property and other proprietary rights, in and to the React + * Native CustomComponents software (the “Software”). Subject to your + * compliance with these terms, you are hereby granted a non-exclusive, + * worldwide, royalty-free copyright license to (1) use and copy the Software; + * and (2) reproduce and distribute the Software as part of your own software + * (“Your Software”). Facebook reserves all rights not expressly granted to + * you in this license agreement. + * + * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. + * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR + * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +'use strict'; + +jest + .dontMock('NavigationEvent') + .dontMock('invariant'); + +var NavigationEvent = require('NavigationEvent'); + +describe('NavigationEvent', () => { + it('constructs', () => { + var target = {}; + var event = new NavigationEvent('foo', target, 123); + expect(event.type).toBe('foo'); + expect(event.target).toBe(target); + expect(event.data).toBe(123); + }); + + it('constructs from pool', () => { + var target = {}; + var event = NavigationEvent.pool('foo', target, 123); + expect(event.type).toBe('foo'); + expect(event.target).toBe(target); + expect(event.data).toBe(123); + }); + + it('prevents default', () => { + var event = new NavigationEvent('foo', {}, 123); + expect(event.defaultPrevented).toBe(false); + event.preventDefault(); + expect(event.defaultPrevented).toBe(true); + }); + + it('recycles', () => { + var event1 = NavigationEvent.pool('foo', {}, 123); + event1.dispose(); + expect(event1.type).toBe(null); + expect(event1.data).toBe(null); + expect(event1.target).toBe(null); + + var event2 = NavigationEvent.pool('bar', {}, 456); + expect(event2.type).toBe('bar'); + expect(event2).toBe(event1); + }); +}); + + From 5ec60effea748b862f55d4b4136a93b351f80dc3 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Thu, 16 Jul 2015 17:35:45 -0700 Subject: [PATCH 15/17] [ReactNative] revert 'unbreak navigation due to onItemRef removal' --- Libraries/CustomComponents/Navigator/Navigator.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index 5121b108e..573223771 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -45,7 +45,6 @@ var View = require('View'); var clamp = require('clamp'); var flattenStyle = require('flattenStyle'); -var guid = require('guid'); var invariant = require('invariant'); var rebound = require('rebound'); @@ -1013,14 +1012,14 @@ var Navigator = React.createClass({ } }, - _renderScene: function(route, i, key) { + _renderScene: function(route, i) { var disabledSceneStyle = null; if (i !== this.state.presentedIndex) { disabledSceneStyle = styles.disabledScene; } return ( { return (this.state.transitionFromIndex != null) || (this.state.transitionFromIndex != null); @@ -1053,7 +1052,7 @@ var Navigator = React.createClass({ index !== this.state.presentedIndex) { renderedScene = this._renderedSceneMap.get(route); } else { - renderedScene = this._renderScene(route, index, guid()); + renderedScene = this._renderScene(route, index); } newRenderedSceneMap.set(route, renderedScene); return renderedScene; From 77a0cf27f2920e4d9ee1bddcfe0cca74a1df33d6 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Thu, 16 Jul 2015 17:35:54 -0700 Subject: [PATCH 16/17] [ReactNative] Revert onItemRef removal --- .../CustomComponents/Navigator/Navigator.js | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index 573223771..bc5d65791 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -220,6 +220,11 @@ var Navigator = React.createClass({ */ onDidFocus: PropTypes.func, + /** + * Will be called with (ref, indexInStack, route) when the scene ref changes + */ + onItemRef: PropTypes.func, + /** * Optionally provide a navigation bar that persists across scene * transitions @@ -313,6 +318,7 @@ var Navigator = React.createClass({ onPanResponderMove: this._handlePanResponderMove, onPanResponderTerminate: this._handlePanResponderTerminate, }); + this._itemRefs = {}; this._interactionHandle = null; this._emitWillFocus(this.state.routeStack[this.state.presentedIndex]); }, @@ -1000,10 +1006,22 @@ var Navigator = React.createClass({ return this.state.routeStack.slice(); }, + _handleItemRef: function(itemId, route, ref) { + this._itemRefs[itemId] = ref; + var itemIndex = this.state.idStack.indexOf(itemId); + if (itemIndex === -1) { + return; + } + this.props.onItemRef && this.props.onItemRef(ref, itemIndex, route); + }, + _cleanScenesPastIndex: function(index) { var newStackLength = index + 1; // Remove any unneeded rendered routes. if (newStackLength < this.state.routeStack.length) { + this.state.idStack.slice(newStackLength).map((removingId) => { + this._itemRefs[removingId] = null; + }); this.setState({ sceneConfigStack: this.state.sceneConfigStack.slice(0, newStackLength), idStack: this.state.idStack.slice(0, newStackLength), @@ -1013,22 +1031,38 @@ var Navigator = React.createClass({ }, _renderScene: function(route, i) { + var child = this.props.renderScene( + route, + this + ); var disabledSceneStyle = null; if (i !== this.state.presentedIndex) { disabledSceneStyle = styles.disabledScene; } + var originalRef = child.ref; + if (originalRef != null && typeof originalRef !== 'function') { + console.warn( + 'String refs are not supported for navigator scenes. Use a callback ' + + 'ref instead. Ignoring ref: ' + originalRef + ); + originalRef = null; + } return ( { return (this.state.transitionFromIndex != null) || (this.state.transitionFromIndex != null); }} style={[styles.baseScene, this.props.sceneStyle, disabledSceneStyle]}> - {this.props.renderScene( - route, - this - )} + {React.cloneElement(child, { + ref: component => { + this._handleItemRef(this.state.idStack[i], route, component); + if (originalRef) { + originalRef(component); + } + } + })} ); }, From c43e93d1b434357564a733b8ee30d3b5dd27ebe4 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Fri, 17 Jul 2015 03:53:15 -0700 Subject: [PATCH 17/17] Reverted ca9d1b3bf5a6f46ec29babe8685634690ff9a2bc to unbreak groups --- .../RCTUIManagerScenarioTests.m | 477 ++++----------- .../testViewExample_1@2x.png | Bin 99475 -> 99477 bytes .../UIExplorerUnitTests/RCTUIManagerTests.m | 24 +- Libraries/Components/ScrollView/ScrollView.js | 1 - Libraries/Components/View/View.js | 6 - .../ReactNative/ReactNativeViewAttributes.js | 16 - Libraries/Text/RCTShadowRawText.m | 5 - React/Modules/RCTUIManager.m | 544 ++++-------------- React/Views/RCTShadowView.h | 6 - React/Views/RCTShadowView.m | 94 +-- React/Views/RCTViewNodeProtocol.h | 3 +- 11 files changed, 234 insertions(+), 942 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m index d6981fba2..c7d30539f 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m @@ -15,46 +15,21 @@ #import #import - -#import "RCTShadowView.h" -#import "RCTUIManager.h" -#import "RCTRootView.h" #import "RCTSparseArray.h" +#import "RCTUIManager.h" #import "UIView+React.h" @interface RCTUIManager (Testing) - (void)_manageChildren:(NSNumber *)containerReactTag + moveFromIndices:(NSArray *)moveFromIndices + moveToIndices:(NSArray *)moveToIndices addChildReactTags:(NSArray *)addChildReactTags addAtIndices:(NSArray *)addAtIndices removeAtIndices:(NSArray *)removeAtIndices registry:(RCTSparseArray *)registry; -- (void)modifyManageChildren:(NSNumber *)containerReactTag - addChildReactTags:(NSMutableArray *)mutableAddChildReactTags - addAtIndices:(NSMutableArray *)mutableAddAtIndices - removeAtIndices:(NSMutableArray *)mutableRemoveAtIndices; - -- (void)createView:(NSNumber *)reactTag - viewName:(NSString *)viewName - rootTag:(NSNumber *)rootTag - props:(NSDictionary *)props; - -- (void)updateView:(NSNumber *)reactTag - viewName:(NSString *)viewName - props:(NSDictionary *)props; - -- (void)manageChildren:(NSNumber *)containerReactTag - moveFromIndices:(NSArray *)moveFromIndices - moveToIndices:(NSArray *)moveToIndices - addChildReactTags:(NSArray *)addChildReactTags - addAtIndices:(NSArray *)addAtIndices - removeAtIndices:(NSArray *)removeAtIndices; - -- (void)flushUIBlocks; - @property (nonatomic, readonly) RCTSparseArray *viewRegistry; -@property (nonatomic, readonly) RCTSparseArray *shadowViewRegistry; // RCT thread only @end @@ -77,360 +52,140 @@ UIView *registeredView = [[UIView alloc] init]; [registeredView setReactTag:@(i)]; _uiManager.viewRegistry[i] = registeredView; - - RCTShadowView *registeredShadowView = [[RCTShadowView alloc] init]; - registeredShadowView.viewName = @"RCTView"; - [registeredShadowView setReactTag:@(i)]; - _uiManager.shadowViewRegistry[i] = registeredShadowView; } } -/* +-----------------------------------------------------------+ +----------------------+ - * | Shadow Hierarchy | | Legend | - * +-----------------------------------------------------------+ +----------------------+ - * | | | | - * | +---+ ****** | | ******************** | - * | | 1 | * 11 * | | * Layout-only View * | - * | +---+ ****** | | ******************** | - * | | | | | | - * | +-------+---+---+----------+ +---+---+ | | +----+ | - * | | | | | | | | | |View| Subview | - * | v v v v v v | | +----+ -----------> | - * | ***** +---+ ***** +---+ +----+ +----+ | | | - * | * 2 * | 3 | * 4 * | 5 | | 12 | | 13 | | +----------------------+ - * | ***** +---+ ***** +---+ +----+ +----+ | - * | | | | | - * | +---+--+ | +---+---+ | - * | | | | | | | - * | v v v v v | - * | +---+ +---+ +---+ +---+ ****** | - * | | 6 | | 7 | | 8 | | 9 | * 10 * | - * | +---+ +---+ +---+ +---+ ****** | - * | | - * +-----------------------------------------------------------+ - * - * +-----------------------------------------------------------+ - * | View Hierarchy | - * +-----------------------------------------------------------+ - * | | - * | +---+ ****** | - * | | 1 | * 11 * | - * | +---+ ****** | - * | | | | - * | +------+------+------+------+ +---+---+ | - * | | | | | | | | | - * | v v v v v v v | - * | +---+ +---+ +---+ +---+ +---+ +----+ +----+ | - * | | 6 | | 7 | | 3 | | 8 | | 5 | | 12 | | 13 | | - * | +---+ +---+ +---+ +---+ +---+ +----+ +----+ | - * | | | - * | v | - * | +---+ | - * | | 9 | | - * | +---+ | - * | | - * +-----------------------------------------------------------+ - */ - -- (void)updateShadowViewWithReactTag:(NSNumber *)reactTag layoutOnly:(BOOL)isLayoutOnly childTags:(NSArray *)childTags +- (void)testManagingChildrenToAddViews { - RCTShadowView *shadowView = _uiManager.shadowViewRegistry[reactTag]; - shadowView.allProps = isLayoutOnly ? @{} : @{@"collapsible": @NO}; - [childTags enumerateObjectsUsingBlock:^(NSNumber *childTag, NSUInteger idx, __unused BOOL *stop) { - [shadowView insertReactSubview:_uiManager.shadowViewRegistry[childTag] atIndex:idx]; - }]; -} + UIView *containerView = _uiManager.viewRegistry[20]; + NSMutableArray *addedViews = [NSMutableArray array]; -- (void)setUpShadowViewHierarchy -{ - [self updateShadowViewWithReactTag:@1 layoutOnly:NO childTags:@[@2, @3, @4, @5]]; - [self updateShadowViewWithReactTag:@2 layoutOnly:YES childTags:@[@6, @7]]; - [self updateShadowViewWithReactTag:@3 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@4 layoutOnly:YES childTags:@[@8]]; - [self updateShadowViewWithReactTag:@5 layoutOnly:NO childTags:@[@9, @10]]; - [self updateShadowViewWithReactTag:@6 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@7 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@8 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@9 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@10 layoutOnly:YES childTags:nil]; - [self updateShadowViewWithReactTag:@11 layoutOnly:YES childTags:@[@12, @13]]; - [self updateShadowViewWithReactTag:@12 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@13 layoutOnly:NO childTags:nil]; -} - -- (void)testModifyIndices1 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[@2] mutableCopy]; - NSMutableArray *addIndices = [@[@3] mutableCopy]; - NSMutableArray *removeIndices = [@[@0] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[@6, @7])); - XCTAssertEqualObjects(addIndices, (@[@3, @4])); - XCTAssertEqualObjects(removeIndices, (@[@0, @1])); -} - -- (void)testModifyIndices2 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[@11] mutableCopy]; - NSMutableArray *addIndices = [@[@4] mutableCopy]; - NSMutableArray *removeIndices = [@[] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[@12, @13])); - XCTAssertEqualObjects(addIndices, (@[@5, @6])); - XCTAssertEqualObjects(removeIndices, (@[])); -} - -- (void)testModifyIndices3 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[] mutableCopy]; - NSMutableArray *addIndices = [@[] mutableCopy]; - NSMutableArray *removeIndices = [@[@2] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[])); - XCTAssertEqualObjects(addIndices, (@[])); - XCTAssertEqualObjects(removeIndices, (@[@3])); -} - -- (void)testModifyIndices4 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[@11] mutableCopy]; - NSMutableArray *addIndices = [@[@3] mutableCopy]; - NSMutableArray *removeIndices = [@[@2] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[@12, @13])); - XCTAssertEqualObjects(addIndices, (@[@4, @5])); - XCTAssertEqualObjects(removeIndices, (@[@3])); -} - -- (void)testModifyIndices5 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[] mutableCopy]; - NSMutableArray *addIndices = [@[] mutableCopy]; - NSMutableArray *removeIndices = [@[@0, @1, @2, @3] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[])); - XCTAssertEqualObjects(addIndices, (@[])); - XCTAssertEqualObjects(removeIndices, (@[@0, @1, @2, @3, @4])); -} - -- (void)testModifyIndices6 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[@11] mutableCopy]; - NSMutableArray *addIndices = [@[@0] mutableCopy]; - NSMutableArray *removeIndices = [@[@0, @1, @2, @3] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[@12, @13])); - XCTAssertEqualObjects(addIndices, (@[@0, @1])); - XCTAssertEqualObjects(removeIndices, (@[@0, @1, @2, @3, @4])); -} - -- (void)testModifyIndices7 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[@11] mutableCopy]; - NSMutableArray *addIndices = [@[@1] mutableCopy]; - NSMutableArray *removeIndices = [@[@0, @2, @3] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[@12, @13])); - XCTAssertEqualObjects(addIndices, (@[@1, @2])); - XCTAssertEqualObjects(removeIndices, (@[@0, @1, @3, @4])); -} - -- (void)DISABLED_testScenario1 -{ - RCTUIManager *uiManager = [[RCTUIManager alloc] init]; - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil]; - NS_VALID_UNTIL_END_OF_SCOPE RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Test"]; - - __block BOOL done = NO; - - dispatch_queue_t shadowQueue = [uiManager valueForKey:@"shadowQueue"]; - dispatch_async(shadowQueue, ^{ - // Make sure root view finishes loading. - dispatch_sync(dispatch_get_main_queue(), ^{}); - - /* */[uiManager createView:@2 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}]; - /* */[uiManager createView:@3 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}]; - /* V */[uiManager createView:@4 viewName:@"RCTView" rootTag:@1 props:@{@"alignItems":@"center",@"backgroundColor":@"#F5FCFF",@"flex":@1,@"justifyContent":@"center"}]; - /* V */[uiManager createView:@5 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"blue",@"height":@50,@"width":@50}]; - /* */[uiManager createView:@6 viewName:@"RCTView" rootTag:@1 props:@{@"width":@250}]; - /* V */[uiManager createView:@7 viewName:@"RCTView" rootTag:@1 props:@{@"borderWidth":@10,@"margin":@50}]; - /* V */[uiManager createView:@8 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"yellow",@"height":@50}]; - /* V */[uiManager createView:@9 viewName:@"RCTText" rootTag:@1 props:@{@"accessible":@1,@"fontSize":@20,@"isHighlighted":@0,@"margin":@10,@"textAlign":@"center"}]; - /* */[uiManager createView:@10 viewName:@"RCTRawText" rootTag:@1 props:@{@"text":@"This tests removal of layout-only views."}]; - /* */[uiManager manageChildren:@9 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@10] addAtIndices:@[@0] removeAtIndices:nil]; - /* V */[uiManager createView:@12 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"green",@"height":@50}]; - /* */[uiManager manageChildren:@7 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@8,@9,@12] addAtIndices:@[@0,@1,@2] removeAtIndices:nil]; - /* */[uiManager manageChildren:@6 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@7] addAtIndices:@[@0] removeAtIndices:nil]; - /* V */[uiManager createView:@13 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"red",@"height":@50,@"width":@50}]; - /* */[uiManager manageChildren:@4 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@5,@6,@13] addAtIndices:@[@0,@1,@2] removeAtIndices:nil]; - /* */[uiManager manageChildren:@3 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@4] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@2 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@3] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@1 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@2] addAtIndices:@[@0] removeAtIndices:nil]; - - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)8); - done = YES; - }]; - - [uiManager flushUIBlocks]; - }); - - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:1.0]; - while ([date timeIntervalSinceNow] > 0 && !done) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + NSArray *tagsToAdd = @[@1, @2, @3, @4, @5]; + NSArray *addAtIndices = @[@0, @1, @2, @3, @4]; + for (NSNumber *tag in tagsToAdd) { + [addedViews addObject:_uiManager.viewRegistry[tag]]; } - done = NO; - dispatch_async(shadowQueue, ^{ - [uiManager updateView:@7 viewName:@"RCTView" props:@{@"borderWidth":[NSNull null]}]; - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)7); - done = YES; - }]; + // Add views 1-5 to view 20 + [_uiManager _manageChildren:@20 + moveFromIndices:nil + moveToIndices:nil + addChildReactTags:tagsToAdd + addAtIndices:addAtIndices + removeAtIndices:nil + registry:_uiManager.viewRegistry]; - [uiManager flushUIBlocks]; - }); - - date = [NSDate dateWithTimeIntervalSinceNow:1.0]; - while ([date timeIntervalSinceNow] > 0 && !done) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - } - - done = NO; - dispatch_async(shadowQueue, ^{ - [uiManager updateView:@7 viewName:@"RCTView" props:@{@"borderWidth":@10}]; - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)8); - done = YES; - }]; - - [uiManager flushUIBlocks]; - }); - - date = [NSDate dateWithTimeIntervalSinceNow:1.0]; - while ([date timeIntervalSinceNow] > 0 && !done) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + XCTAssertTrue([[containerView reactSubviews] count] == 5, + @"Expect to have 5 react subviews after calling manage children \ + with 5 tags to add, instead have %lu", (unsigned long)[[containerView reactSubviews] count]); + for (UIView *view in addedViews) { + XCTAssertTrue([view superview] == containerView, + @"Expected to have manage children successfully add children"); + [view removeFromSuperview]; } } -- (void)DISABLED_testScenario2 +- (void)testManagingChildrenToRemoveViews { - RCTUIManager *uiManager = [[RCTUIManager alloc] init]; - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil]; - NS_VALID_UNTIL_END_OF_SCOPE RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Test"]; + UIView *containerView = _uiManager.viewRegistry[20]; + NSMutableArray *removedViews = [NSMutableArray array]; - __block BOOL done = NO; - - dispatch_queue_t shadowQueue = [uiManager valueForKey:@"shadowQueue"]; - dispatch_async(shadowQueue, ^{ - // Make sure root view finishes loading. - dispatch_sync(dispatch_get_main_queue(), ^{}); - - /* */[uiManager createView:@2 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}]; - /* */[uiManager createView:@3 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}]; - /* V */[uiManager createView:@4 viewName:@"RCTView" rootTag:@1 props:@{@"alignItems":@"center",@"backgroundColor":@"#F5FCFF",@"flex":@1,@"justifyContent":@"center"}]; - /* */[uiManager createView:@5 viewName:@"RCTView" rootTag:@1 props:@{@"width":@250}]; - /* V */[uiManager createView:@6 viewName:@"RCTView" rootTag:@1 props:@{@"borderWidth":@1}]; - /* V */[uiManager createView:@7 viewName:@"RCTText" rootTag:@1 props:@{@"accessible":@1,@"fontSize":@20,@"isHighlighted":@0,@"margin":@10,@"textAlign":@"center"}]; - /* */[uiManager createView:@8 viewName:@"RCTRawText" rootTag:@1 props:@{@"text":@"This tests removal of layout-only views."}]; - /* */[uiManager manageChildren:@7 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@8] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@6 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@7] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@5 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@6] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@4 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@5] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@3 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@4] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@2 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@3] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@1 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@2] addAtIndices:@[@0] removeAtIndices:nil]; - - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)4); - done = YES; - }]; - - [uiManager flushUIBlocks]; - }); - - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:1.0]; - while ([date timeIntervalSinceNow] > 0 && !done) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + NSArray *removeAtIndices = @[@0, @4, @8, @12, @16]; + for (NSNumber *index in removeAtIndices) { + NSNumber *reactTag = @([index integerValue] + 2); + [removedViews addObject:_uiManager.viewRegistry[reactTag]]; + } + for (NSInteger i = 2; i < 20; i++) { + UIView *view = _uiManager.viewRegistry[i]; + [containerView addSubview:view]; } - done = NO; - dispatch_async(shadowQueue, ^{ - [uiManager updateView:@6 viewName:@"RCTView" props:@{@"borderWidth":[NSNull null]}]; - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)3); - done = YES; - }]; + // Remove views 1-5 from view 20 + [_uiManager _manageChildren:@20 + moveFromIndices:nil + moveToIndices:nil + addChildReactTags:nil + addAtIndices:nil + removeAtIndices:removeAtIndices + registry:_uiManager.viewRegistry]; - [uiManager flushUIBlocks]; - }); + XCTAssertEqual(containerView.reactSubviews.count, (NSUInteger)13, + @"Expect to have 13 react subviews after calling manage children\ + with 5 tags to remove and 18 prior children, instead have %zd", + containerView.reactSubviews.count); + for (UIView *view in removedViews) { + XCTAssertTrue([view superview] == nil, + @"Expected to have manage children successfully remove children"); + // After removing views are unregistered - we need to reregister + _uiManager.viewRegistry[view.reactTag] = view; + } + for (NSInteger i = 2; i < 20; i++) { + UIView *view = _uiManager.viewRegistry[i]; + if (![removedViews containsObject:view]) { + XCTAssertTrue([view superview] == containerView, + @"Should not have removed view with react tag %ld during delete but did", (long)i); + [view removeFromSuperview]; + } + } +} - date = [NSDate dateWithTimeIntervalSinceNow:1.0]; - while ([date timeIntervalSinceNow] > 0 && !done) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; +// We want to start with views 1-10 added at indices 0-9 +// Then we'll remove indices 2, 3, 5 and 8 +// Add views 11 and 12 to indices 0 and 6 +// And move indices 4 and 9 to 1 and 7 +// So in total it goes from: +// [1,2,3,4,5,6,7,8,9,10] +// to +// [11,5,1,2,7,8,12,10] +- (void)testManagingChildrenToAddRemoveAndMove +{ + UIView *containerView = _uiManager.viewRegistry[20]; + + NSArray *removeAtIndices = @[@2, @3, @5, @8]; + NSArray *addAtIndices = @[@0, @6]; + NSArray *tagsToAdd = @[@11, @12]; + NSArray *moveFromIndices = @[@4, @9]; + NSArray *moveToIndices = @[@1, @7]; + + // We need to keep these in array to keep them around + NSMutableArray *viewsToRemove = [NSMutableArray array]; + for (NSUInteger i = 0; i < removeAtIndices.count; i++) { + NSNumber *reactTagToRemove = @([removeAtIndices[i] integerValue] + 1); + UIView *viewToRemove = _uiManager.viewRegistry[reactTagToRemove]; + [viewsToRemove addObject:viewToRemove]; } - done = NO; - dispatch_async(shadowQueue, ^{ - [uiManager updateView:@6 viewName:@"RCTView" props:@{@"borderWidth":@1}]; - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)4); - done = YES; - }]; + for (NSInteger i = 1; i < 11; i++) { + UIView *view = _uiManager.viewRegistry[i]; + [containerView addSubview:view]; + } - [uiManager flushUIBlocks]; - }); + [_uiManager _manageChildren:@20 + moveFromIndices:moveFromIndices + moveToIndices:moveToIndices + addChildReactTags:tagsToAdd + addAtIndices:addAtIndices + removeAtIndices:removeAtIndices + registry:_uiManager.viewRegistry]; - date = [NSDate dateWithTimeIntervalSinceNow:1.0]; - while ([date timeIntervalSinceNow] > 0 && !done) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + XCTAssertTrue([[containerView reactSubviews] count] == 8, + @"Expect to have 8 react subviews after calling manage children,\ + instead have the following subviews %@", [containerView reactSubviews]); + + NSArray *expectedReactTags = @[@11, @5, @1, @2, @7, @8, @12, @10]; + for (NSUInteger i = 0; i < containerView.subviews.count; i++) { + XCTAssertEqualObjects([[containerView reactSubviews][i] reactTag], expectedReactTags[i], + @"Expected subview at index %ld to have react tag #%@ but has tag #%@", + (long)i, expectedReactTags[i], [[containerView reactSubviews][i] reactTag]); + } + + // Clean up after ourselves + for (NSInteger i = 1; i < 13; i++) { + UIView *view = _uiManager.viewRegistry[i]; + [view removeFromSuperview]; + } + for (UIView *view in viewsToRemove) { + _uiManager.viewRegistry[view.reactTag] = view; } } diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png index 2451b5f92174ff1514f7dbc474aa4c7bc633fb99..fb2ab9feb2e7b98d46e1a2edd832713606dff0e6 100644 GIT binary patch delta 18714 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 B0~-JU delta 18747 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 g>tr%;5NPr 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 diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m index 0e9290814..b587d9bca 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m @@ -14,8 +14,6 @@ #import -#import "RCTRootView.h" -#import "RCTShadowView.h" #import "RCTSparseArray.h" #import "RCTUIManager.h" #import "UIView+React.h" @@ -23,13 +21,14 @@ @interface RCTUIManager (Testing) - (void)_manageChildren:(NSNumber *)containerReactTag + moveFromIndices:(NSArray *)moveFromIndices + moveToIndices:(NSArray *)moveToIndices addChildReactTags:(NSArray *)addChildReactTags addAtIndices:(NSArray *)addAtIndices removeAtIndices:(NSArray *)removeAtIndices registry:(RCTSparseArray *)registry; @property (nonatomic, readonly) RCTSparseArray *viewRegistry; -@property (nonatomic, readonly) RCTSparseArray *shadowViewRegistry; // RCT thread only @end @@ -52,11 +51,6 @@ UIView *registeredView = [[UIView alloc] init]; [registeredView setReactTag:@(i)]; _uiManager.viewRegistry[i] = registeredView; - - RCTShadowView *registeredShadowView = [[RCTShadowView alloc] init]; - registeredShadowView.viewName = @"RCTView"; - [registeredShadowView setReactTag:@(i)]; - _uiManager.shadowViewRegistry[i] = registeredShadowView; } } @@ -73,6 +67,8 @@ // Add views 1-5 to view 20 [_uiManager _manageChildren:@20 + moveFromIndices:nil + moveToIndices:nil addChildReactTags:tagsToAdd addAtIndices:addAtIndices removeAtIndices:nil @@ -105,6 +101,8 @@ // Remove views 1-5 from view 20 [_uiManager _manageChildren:@20 + moveFromIndices:nil + moveToIndices:nil addChildReactTags:nil addAtIndices:nil removeAtIndices:removeAtIndices @@ -142,9 +140,11 @@ { UIView *containerView = _uiManager.viewRegistry[20]; - NSArray *removeAtIndices = @[@2, @3, @5, @8, @4, @9]; - NSArray *addAtIndices = @[@0, @6, @1, @7]; - NSArray *tagsToAdd = @[@11, @12, @5, @10]; + NSArray *removeAtIndices = @[@2, @3, @5, @8]; + NSArray *addAtIndices = @[@0, @6]; + NSArray *tagsToAdd = @[@11, @12]; + NSArray *moveFromIndices = @[@4, @9]; + NSArray *moveToIndices = @[@1, @7]; // We need to keep these in array to keep them around NSMutableArray *viewsToRemove = [NSMutableArray array]; @@ -160,6 +160,8 @@ } [_uiManager _manageChildren:@20 + moveFromIndices:moveFromIndices + moveToIndices:moveToIndices addChildReactTags:tagsToAdd addAtIndices:addAtIndices removeAtIndices:removeAtIndices diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index f01cce58a..512d8dbd6 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -279,7 +279,6 @@ var ScrollView = React.createClass({ var contentContainer = diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 6912c05db..707d97b7e 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -83,12 +83,6 @@ var View = React.createClass({ }, propTypes: { - /** - * When false, indicates that the view should not be collapsed, even if it is - * layout-only. Defaults to true. - */ - collapsible: PropTypes.bool, - /** * When true, indicates that the view is an accessibility element. By default, * all the touchable elements are accessible. diff --git a/Libraries/ReactNative/ReactNativeViewAttributes.js b/Libraries/ReactNative/ReactNativeViewAttributes.js index 367c6be2d..50b839e1d 100644 --- a/Libraries/ReactNative/ReactNativeViewAttributes.js +++ b/Libraries/ReactNative/ReactNativeViewAttributes.js @@ -24,22 +24,6 @@ ReactNativeViewAttributes.UIView = { onLayout: true, onAccessibilityTap: true, onMagicTap: true, - collapsible: true, - - // If editing layout-only view attributes, make sure - // -[RCTShadowView isLayoutOnly] in RCTShadowView.m - // is up-to-date! If any property below is set, the - // view should not be collapsible, but this is done - // on the native side. - onMoveShouldSetResponder: true, - onResponderGrant: true, - onResponderMove: true, - onResponderReject: true, - onResponderRelease: true, - onResponderTerminate: true, - onResponderTerminationRequest: true, - onStartShouldSetResponder: true, - onStartShouldSetResponderCapture: true, }; ReactNativeViewAttributes.RCTView = merge( diff --git a/Libraries/Text/RCTShadowRawText.m b/Libraries/Text/RCTShadowRawText.m index 00a3490bc..e99e1187b 100644 --- a/Libraries/Text/RCTShadowRawText.m +++ b/Libraries/Text/RCTShadowRawText.m @@ -20,11 +20,6 @@ } } -- (BOOL)isLayoutOnly -{ - return YES; -} - - (NSString *)description { NSString *superDescription = super.description; diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 0b3c2c043..724ace6e6 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -32,8 +32,15 @@ #import "RCTViewNodeProtocol.h" #import "UIView+React.h" -static void RCTTraverseViewNodes(id view, void (^block)(id)); -static NSDictionary *RCTPropsMerge(NSDictionary *beforeProps, NSDictionary *newProps); +typedef void (^react_view_node_block_t)(id); + +static void RCTTraverseViewNodes(id view, react_view_node_block_t block) +{ + if (view.reactTag) block(view); + for (id subview in view.reactSubviews) { + RCTTraverseViewNodes(subview, block); + } +} @interface RCTAnimation : NSObject @@ -460,24 +467,6 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass) [rootShadowView collectRootUpdatedFrames:viewsWithNewFrames parentConstraint:(CGSize){CSS_UNDEFINED, CSS_UNDEFINED}]; - NSSet *originalViewsWithNewFrames = [viewsWithNewFrames copy]; - NSMutableArray *viewsToCheck = [viewsWithNewFrames.allObjects mutableCopy]; - while (viewsToCheck.count > 0) { - // Better to remove from the front and append to the end - // because of how NSMutableArray is implemented. - // (It's a "round" buffer with stored size and offset.) - - RCTShadowView *viewToCheck = viewsToCheck.firstObject; - [viewsToCheck removeObjectAtIndex:0]; - - if (viewToCheck.layoutOnly) { - [viewsWithNewFrames removeObject:viewToCheck]; - [viewsToCheck addObjectsFromArray:[viewToCheck reactSubviews]]; - } else { - [viewsWithNewFrames addObject:viewToCheck]; - } - } - // Parallel arrays are built and then handed off to main thread NSMutableArray *frameReactTags = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count]; NSMutableArray *frames = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count]; @@ -486,30 +475,26 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass) NSMutableArray *onLayoutEvents = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count]; for (RCTShadowView *shadowView in viewsWithNewFrames) { - CGRect frame = shadowView.adjustedFrame; - NSNumber *reactTag = shadowView.reactTag; - [frameReactTags addObject:reactTag]; - [frames addObject:[NSValue valueWithCGRect:frame]]; + [frameReactTags addObject:shadowView.reactTag]; + [frames addObject:[NSValue valueWithCGRect:shadowView.frame]]; [areNew addObject:@(shadowView.isNewView)]; - - RCTShadowView *superview = shadowView; - BOOL parentIsNew = NO; - while (YES) { - superview = superview.superview; - parentIsNew = superview.isNewView; - if (!superview.layoutOnly) { - break; - } + [parentsAreNew addObject:@(shadowView.superview.isNewView)]; + id event = (id)kCFNull; + if (shadowView.hasOnLayout) { + event = @{ + @"target": shadowView.reactTag, + @"layout": @{ + @"x": @(shadowView.frame.origin.x), + @"y": @(shadowView.frame.origin.y), + @"width": @(shadowView.frame.size.width), + @"height": @(shadowView.frame.size.height), + }, + }; } - [parentsAreNew addObject:@(parentIsNew)]; - - id event = shadowView.hasOnLayout - ? RCTShadowViewOnLayoutEventPayload(shadowView.reactTag, frame) - : (id)kCFNull; [onLayoutEvents addObject:event]; } - for (RCTShadowView *shadowView in originalViewsWithNewFrames) { + for (RCTShadowView *shadowView in viewsWithNewFrames) { // We have to do this after we build the parentsAreNew array. shadowView.newView = NO; } @@ -526,28 +511,24 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass) } // Perform layout (possibly animated) - return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { - RCTResponseSenderBlock callback = uiManager->_layoutAnimation.callback; + return ^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + RCTResponseSenderBlock callback = self->_layoutAnimation.callback; __block NSUInteger completionsCalled = 0; for (NSUInteger ii = 0; ii < frames.count; ii++) { NSNumber *reactTag = frameReactTags[ii]; UIView *view = viewRegistry[reactTag]; - if (!view) { - continue; - } - CGRect frame = [frames[ii] CGRectValue]; id event = onLayoutEvents[ii]; BOOL isNew = [areNew[ii] boolValue]; - RCTAnimation *updateAnimation = isNew ? nil : uiManager->_layoutAnimation.updateAnimation; + RCTAnimation *updateAnimation = isNew ? nil : _layoutAnimation.updateAnimation; BOOL shouldAnimateCreation = isNew && ![parentsAreNew[ii] boolValue]; - RCTAnimation *createAnimation = shouldAnimateCreation ? uiManager->_layoutAnimation.createAnimation : nil; + RCTAnimation *createAnimation = shouldAnimateCreation ? _layoutAnimation.createAnimation : nil; void (^completion)(BOOL) = ^(BOOL finished) { completionsCalled++; if (event != (id)kCFNull) { - [uiManager.bridge.eventDispatcher sendInputEventWithName:@"topLayout" body:event]; + [self.bridge.eventDispatcher sendInputEventWithName:@"topLayout" body:event]; } if (callback && completionsCalled == frames.count - 1) { callback(@[@(finished)]); @@ -559,13 +540,13 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass) [updateAnimation performAnimations:^{ [view reactSetFrame:frame]; for (RCTViewManagerUIBlock block in updateBlocks) { - block(uiManager, viewRegistry); + block(self, _viewRegistry); } } withCompletionBlock:completion]; } else { [view reactSetFrame:frame]; for (RCTViewManagerUIBlock block in updateBlocks) { - block(uiManager, viewRegistry); + block(self, _viewRegistry); } completion(YES); } @@ -587,7 +568,7 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass) createAnimation.property); } for (RCTViewManagerUIBlock block in updateBlocks) { - block(uiManager, viewRegistry); + block(self, _viewRegistry); } } withCompletionBlock:nil]; } @@ -710,135 +691,6 @@ RCT_EXPORT_METHOD(replaceExistingNonRootView:(NSNumber *)reactTag withView:(NSNu removeAtIndices:removeAtIndices]; } -/** - * This method modifies the indices received in manageChildren() to take into - * account views that are layout only. For example, if JS tells native to insert - * view with tag 12 at index 4, but view 12 is layout only, we would want to - * insert its children's tags, tags 13 and 14, at indices 4 and 5 instead. This - * causes us to have to shift the remaining indices to account for the new - * views. - */ -- (void)modifyManageChildren:(NSNumber *)containerReactTag - addChildReactTags:(NSMutableArray *)mutableAddChildReactTags - addAtIndices:(NSMutableArray *)mutableAddAtIndices - removeAtIndices:(NSMutableArray *)mutableRemoveAtIndices -{ - NSUInteger i; - NSMutableArray *containerSubviews = [[_shadowViewRegistry[containerReactTag] reactSubviews] mutableCopy]; - - i = 0; - while (i < containerSubviews.count) { - RCTShadowView *shadowView = containerSubviews[i]; - if (!shadowView.layoutOnly) { - i++; - continue; - } - - [containerSubviews removeObjectAtIndex:i]; - - NSArray *subviews = [shadowView reactSubviews]; - NSUInteger subviewsCount = subviews.count; - NSIndexSet *insertionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(i, subviewsCount)]; - [containerSubviews insertObjects:subviews atIndexes:insertionIndexes]; - - NSUInteger removalIndex = [mutableRemoveAtIndices indexOfObject:@(i)]; - if (removalIndex != NSNotFound) { - [mutableRemoveAtIndices removeObjectAtIndex:removalIndex]; - } - - if (subviewsCount != 1) { - for (NSUInteger j = 0, count = mutableRemoveAtIndices.count; j < count; j++) { - NSUInteger atIndex = [mutableRemoveAtIndices[j] unsignedIntegerValue]; - if (atIndex > i) { - mutableRemoveAtIndices[j] = @(atIndex + subviewsCount - 1); - } - } - } - - if (removalIndex != NSNotFound) { - for (NSUInteger j = 0; j < subviewsCount; j++) { - [mutableRemoveAtIndices insertObject:@(i + j) atIndex:removalIndex + j]; - } - } - - if (removalIndex == NSNotFound && subviewsCount != 1) { - for (NSUInteger j = 0, count = mutableAddAtIndices.count; j < count; j++) { - NSUInteger atIndex = [mutableAddAtIndices[j] unsignedIntegerValue]; - if (atIndex > i) { - mutableAddAtIndices[j] = @(atIndex + subviewsCount - 1); - } - } - } - } - - i = 0; - while (i < mutableAddChildReactTags.count) { - NSNumber *tag = mutableAddChildReactTags[i]; - NSNumber *index = mutableAddAtIndices[i]; - - RCTShadowView *shadowView = _shadowViewRegistry[tag]; - if (!shadowView.layoutOnly) { - i++; - continue; - } - - NSArray *subviews = [shadowView reactSubviews]; - NSUInteger subviewsCount = subviews.count; - [mutableAddAtIndices removeObjectAtIndex:i]; - [mutableAddChildReactTags removeObjectAtIndex:i]; - - for (NSUInteger j = 0; j < subviewsCount; j++) { - [mutableAddChildReactTags insertObject:[subviews[j] reactTag] atIndex:i + j]; - [mutableAddAtIndices insertObject:@(index.unsignedIntegerValue + j) atIndex:i + j]; - } - - for (NSUInteger j = i + subviewsCount, count = mutableAddAtIndices.count; j < count; j++) { - NSUInteger atIndex = [mutableAddAtIndices[j] unsignedIntegerValue]; - mutableAddAtIndices[j] = @(atIndex + subviewsCount - 1); - } - } -} - -- (NSNumber *)containerReactTag:(NSNumber *)containerReactTag offset:(inout NSUInteger *)offset -{ - RCTShadowView *container = _shadowViewRegistry[containerReactTag]; - NSNumber *containerSuperviewReactTag = containerReactTag; - RCTShadowView *superview = container; - - while (superview.layoutOnly) { - RCTShadowView *superviewSuperview = superview.superview; - containerSuperviewReactTag = superviewSuperview.reactTag; - NSMutableArray *reactSubviews = [[superviewSuperview reactSubviews] mutableCopy]; - NSUInteger superviewIndex = [reactSubviews indexOfObject:superview]; - - NSUInteger i = 0; - while (i < superviewIndex) { - RCTShadowView *child = reactSubviews[i]; - if (!child.layoutOnly) { - if (offset) { - (*offset)++; - } - - i++; - continue; - } - - [reactSubviews removeObjectAtIndex:i]; - - NSArray *subviews = [child reactSubviews]; - NSUInteger subviewsCount = subviews.count; - NSIndexSet *insertionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(i, subviewsCount)]; - [reactSubviews insertObjects:subviews atIndexes:insertionIndexes]; - - superviewIndex += subviewsCount - 1; - } - - superview = superviewSuperview; - } - - return containerSuperviewReactTag; -} - RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag moveFromIndices:(NSArray *)moveFromIndices moveToIndices:(NSArray *)moveToIndices @@ -846,109 +698,62 @@ RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag addAtIndices:(NSArray *)addAtIndices removeAtIndices:(NSArray *)removeAtIndices) { - RCTShadowView *container = _shadowViewRegistry[containerReactTag]; - NSUInteger offset = 0; - NSNumber *containerSuperviewReactTag = [self containerReactTag:containerReactTag offset:&offset]; - - RCTAssert(moveFromIndices.count == moveToIndices.count, @"Invalid argument: moveFromIndices.count != moveToIndices.count"); - if (moveFromIndices.count > 0) { - NSMutableArray *mutableAddChildReactTags = [addChildReactTags mutableCopy]; - NSMutableArray *mutableAddAtIndices = [addAtIndices mutableCopy]; - NSMutableArray *mutableRemoveAtIndices = [removeAtIndices mutableCopy]; - - NSArray *containerSubviews = [container reactSubviews]; - for (NSUInteger i = 0, count = moveFromIndices.count; i < count; i++) { - NSNumber *from = moveFromIndices[i]; - NSNumber *to = moveToIndices[i]; - [mutableAddChildReactTags addObject:[containerSubviews[from.unsignedIntegerValue] reactTag]]; - [mutableAddAtIndices addObject:to]; - [mutableRemoveAtIndices addObject:from]; - } - - addChildReactTags = mutableAddChildReactTags; - addAtIndices = mutableAddAtIndices; - removeAtIndices = mutableRemoveAtIndices; - } - - NSMutableArray *mutableAddChildReactTags; - NSMutableArray *mutableAddAtIndices; - NSMutableArray *mutableRemoveAtIndices; - - if (containerSuperviewReactTag) { - mutableAddChildReactTags = [addChildReactTags mutableCopy]; - mutableAddAtIndices = [addAtIndices mutableCopy]; - mutableRemoveAtIndices = [removeAtIndices mutableCopy]; - - [self modifyManageChildren:containerReactTag - addChildReactTags:mutableAddChildReactTags - addAtIndices:mutableAddAtIndices - removeAtIndices:mutableRemoveAtIndices]; - - if (offset > 0) { - NSUInteger count = MAX(mutableAddAtIndices.count, mutableRemoveAtIndices.count); - for (NSUInteger i = 0; i < count; i++) { - if (i < mutableAddAtIndices.count) { - NSUInteger index = [mutableAddAtIndices[i] unsignedIntegerValue]; - mutableAddAtIndices[i] = @(index + offset); - } - - if (i < mutableRemoveAtIndices.count) { - NSUInteger index = [mutableRemoveAtIndices[i] unsignedIntegerValue]; - mutableRemoveAtIndices[i] = @(index + offset); - } - } - } - } - [self _manageChildren:containerReactTag + moveFromIndices:moveFromIndices + moveToIndices:moveToIndices addChildReactTags:addChildReactTags addAtIndices:addAtIndices removeAtIndices:removeAtIndices registry:_shadowViewRegistry]; - if (containerSuperviewReactTag) { - [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ - (void)(id []){containerReactTag, @(offset), addChildReactTags, addAtIndices, removeAtIndices}; - [uiManager _manageChildren:containerSuperviewReactTag - addChildReactTags:mutableAddChildReactTags - addAtIndices:mutableAddAtIndices - removeAtIndices:mutableRemoveAtIndices - registry:viewRegistry]; - }]; - } + [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ + [uiManager _manageChildren:containerReactTag + moveFromIndices:moveFromIndices + moveToIndices:moveToIndices + addChildReactTags:addChildReactTags + addAtIndices:addAtIndices + removeAtIndices:removeAtIndices + registry:viewRegistry]; + }]; } - (void)_manageChildren:(NSNumber *)containerReactTag + moveFromIndices:(NSArray *)moveFromIndices + moveToIndices:(NSArray *)moveToIndices addChildReactTags:(NSArray *)addChildReactTags addAtIndices:(NSArray *)addAtIndices removeAtIndices:(NSArray *)removeAtIndices registry:(RCTSparseArray *)registry { id container = registry[containerReactTag]; - RCTAssert(addChildReactTags.count == addAtIndices.count, @"Invalid arguments: addChildReactTags.count == addAtIndices.count"); + RCTAssert(moveFromIndices.count == moveToIndices.count, @"moveFromIndices had size %tu, moveToIndices had size %tu", moveFromIndices.count, moveToIndices.count); + RCTAssert(addChildReactTags.count == addAtIndices.count, @"there should be at least one React child to add"); - // Removes are using "before" indices - NSArray *removedChildren = [self _childrenToRemoveFromContainer:container atIndices:removeAtIndices]; - [self _removeChildren:removedChildren fromContainer:container]; + // Removes (both permanent and temporary moves) are using "before" indices + NSArray *permanentlyRemovedChildren = [self _childrenToRemoveFromContainer:container atIndices:removeAtIndices]; + NSArray *temporarilyRemovedChildren = [self _childrenToRemoveFromContainer:container atIndices:moveFromIndices]; + [self _removeChildren:permanentlyRemovedChildren fromContainer:container]; + [self _removeChildren:temporarilyRemovedChildren fromContainer:container]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT reactTag in %@", addChildReactTags]; - NSArray *permanentlyRemovedChildren = [removedChildren filteredArrayUsingPredicate:predicate]; [self _purgeChildren:permanentlyRemovedChildren fromRegistry:registry]; // TODO (#5906496): optimize all these loops - constantly calling array.count is not efficient - // Figure out what to insert - NSMutableDictionary *childrenToAdd = [NSMutableDictionary dictionary]; - for (NSInteger index = 0, count = addAtIndices.count; index < count; index++) { - id view = registry[addChildReactTags[index]]; + // Figure out what to insert - merge temporary inserts and adds + NSMutableDictionary *destinationsToChildrenToAdd = [NSMutableDictionary dictionary]; + for (NSInteger index = 0, length = temporarilyRemovedChildren.count; index < length; index++) { + destinationsToChildrenToAdd[moveToIndices[index]] = temporarilyRemovedChildren[index]; + } + for (NSInteger index = 0, length = addAtIndices.count; index < length; index++) { + id view = registry[addChildReactTags[index]]; if (view) { - childrenToAdd[addAtIndices[index]] = view; + destinationsToChildrenToAdd[addAtIndices[index]] = view; } } - NSArray *sortedIndices = [[childrenToAdd allKeys] sortedArrayUsingSelector:@selector(compare:)]; + NSArray *sortedIndices = [[destinationsToChildrenToAdd allKeys] sortedArrayUsingSelector:@selector(compare:)]; for (NSNumber *reactIndex in sortedIndices) { - [container insertReactSubview:childrenToAdd[reactIndex] atIndex:reactIndex.integerValue]; + [container insertReactSubview:destinationsToChildrenToAdd[reactIndex] atIndex:reactIndex.integerValue]; } } @@ -1031,72 +836,45 @@ RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag // Set properties shadowView.viewName = viewName; shadowView.reactTag = reactTag; - shadowView.allProps = props; RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], manager); } _shadowViewRegistry[reactTag] = shadowView; - if (!shadowView.layoutOnly) { - // Shadow view is the source of truth for background color this is a little - // bit counter-intuitive if people try to set background color when setting up - // the view, but it's the only way that makes sense given our threading model - UIColor *backgroundColor = shadowView.backgroundColor; - [self addUIBlock:^(RCTUIManager *uiManager, __unused RCTSparseArray *viewRegistry) { - [uiManager createView:reactTag viewName:viewName props:props withManager:manager backgroundColor:backgroundColor]; - }]; - } -} + // Shadow view is the source of truth for background color this is a little + // bit counter-intuitive if people try to set background color when setting up + // the view, but it's the only way that makes sense given our threading model + UIColor *backgroundColor = shadowView.backgroundColor; -- (UIView *)createView:(NSNumber *)reactTag viewName:(NSString *)viewName props:(NSDictionary *)props withManager:(RCTViewManager *)manager backgroundColor:(UIColor *)backgroundColor -{ - RCTAssertMainThread(); - UIView *view = [manager view]; - if (!view) { - return nil; - } + [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ + RCTAssertMainThread(); - // Generate default view, used for resetting default props - if (!_defaultViews[viewName]) { - // Note the default is setup after the props are read for the first time - // ever for this className - this is ok because we only use the default - // for restoring defaults, which never happens on first creation. - _defaultViews[viewName] = [manager view]; - } + UIView *view = [manager view]; + if (view) { - // Set properties - view.reactTag = reactTag; - view.backgroundColor = backgroundColor; - if ([view isKindOfClass:[UIView class]]) { - view.multipleTouchEnabled = YES; - view.userInteractionEnabled = YES; // required for touch handling - view.layer.allowsGroupOpacity = YES; // required for touch handling - } - RCTSetViewProps(props, view, _defaultViews[viewName], manager); + // Generate default view, used for resetting default props + if (!uiManager->_defaultViews[viewName]) { + // Note the default is setup after the props are read for the first time + // ever for this className - this is ok because we only use the default + // for restoring defaults, which never happens on first creation. + uiManager->_defaultViews[viewName] = [manager view]; + } - if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) { - [_bridgeTransactionListeners addObject:view]; - } - _viewRegistry[reactTag] = view; + // Set properties + view.reactTag = reactTag; + view.backgroundColor = backgroundColor; + if ([view isKindOfClass:[UIView class]]) { + view.multipleTouchEnabled = YES; + view.userInteractionEnabled = YES; // required for touch handling + view.layer.allowsGroupOpacity = YES; // required for touch handling + } + RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], manager); - return view; -} - -NS_INLINE BOOL RCTRectIsDefined(CGRect frame) -{ - return !(isnan(frame.origin.x) || isnan(frame.origin.y) || isnan(frame.size.width) || isnan(frame.size.height)); -} - -NS_INLINE NSDictionary *RCTShadowViewOnLayoutEventPayload(NSNumber *reactTag, CGRect frame) -{ - return @{ - @"target": reactTag, - @"layout": @{ - @"x": @(frame.origin.x), - @"y": @(frame.origin.y), - @"width": @(frame.size.width), - @"height": @(frame.size.height), - }, - }; + if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) { + [uiManager->_bridgeTransactionListeners addObject:view]; + } + } + viewRegistry[reactTag] = view; + }]; } // TODO: remove viewName param as it isn't needed @@ -1110,100 +888,10 @@ RCT_EXPORT_METHOD(updateView:(NSNumber *)reactTag RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], viewManager); - const BOOL wasLayoutOnly = shadowView.layoutOnly; - NSDictionary *newProps = RCTPropsMerge(shadowView.allProps, props); - shadowView.allProps = newProps; - - const BOOL isLayoutOnly = shadowView.layoutOnly; - - if (wasLayoutOnly != isLayoutOnly) { - // Add/remove node - - if (isLayoutOnly) { - [self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { - RCTAssertMainThread(); - - UIView *container = viewRegistry[reactTag]; - - const CGRect containerFrame = container.frame; - const CGFloat deltaX = containerFrame.origin.x; - const CGFloat deltaY = containerFrame.origin.y; - - NSUInteger offset = [container.superview.subviews indexOfObject:container]; - [container.subviews enumerateObjectsUsingBlock:^(UIView *subview, NSUInteger idx, __unused BOOL *stop) { - [container removeReactSubview:subview]; - - CGRect subviewFrame = subview.frame; - subviewFrame.origin.x += deltaX; - subviewFrame.origin.y += deltaY; - subview.frame = subviewFrame; - - [container.superview insertReactSubview:subview atIndex:idx + offset]; - }]; - - [container.superview removeReactSubview:container]; - if ([container conformsToProtocol:@protocol(RCTInvalidating)]) { - [(id)container invalidate]; - } - - viewRegistry[reactTag] = nil; - }]; - } else { - NSMutableArray *mutableAddChildReactTags = [[[shadowView reactSubviews] valueForKey:@"reactTag"] mutableCopy]; - NSMutableArray *mutableAddAtIndices = [NSMutableArray arrayWithCapacity:mutableAddChildReactTags.count]; - for (NSUInteger i = 0, count = mutableAddChildReactTags.count; i < count; i++) { - [mutableAddAtIndices addObject:@(i)]; - } - - [self modifyManageChildren:reactTag - addChildReactTags:mutableAddChildReactTags - addAtIndices:mutableAddAtIndices - removeAtIndices:nil]; - - NSUInteger offset = [shadowView.superview.reactSubviews indexOfObject:shadowView]; - NSNumber *containerSuperviewReactTag = [self containerReactTag:shadowView.superview.reactTag offset:&offset]; - UIColor *backgroundColor = shadowView.backgroundColor; - - CGRect shadowViewFrame = shadowView.adjustedFrame; - NSMutableDictionary *newFrames = [NSMutableDictionary dictionaryWithCapacity:mutableAddChildReactTags.count]; - for (NSNumber *childTag in mutableAddChildReactTags) { - RCTShadowView *child = _shadowViewRegistry[childTag]; - newFrames[childTag] = [NSValue valueWithCGRect:child.adjustedFrame]; - } - - [self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { - RCTAssertMainThread(); - - UIView *containerSuperview = viewRegistry[containerSuperviewReactTag]; - UIView *container = [uiManager createView:reactTag viewName:viewName props:newProps withManager:viewManager backgroundColor:backgroundColor]; - - [containerSuperview insertReactSubview:container atIndex:offset]; - if (RCTRectIsDefined(shadowViewFrame)) { - container.frame = shadowViewFrame; - } - - for (NSUInteger i = 0, count = mutableAddAtIndices.count; i < count; i++) { - NSNumber *tag = mutableAddChildReactTags[i]; - UIView *subview = viewRegistry[tag]; - [containerSuperview removeReactSubview:subview]; - - NSUInteger atIndex = [mutableAddAtIndices[i] unsignedIntegerValue]; - [container insertReactSubview:subview atIndex:atIndex]; - - CGRect subviewFrame = [newFrames[tag] CGRectValue]; - if (RCTRectIsDefined(subviewFrame)) { - subview.frame = subviewFrame; - } - } - }]; - } - } else if (!isLayoutOnly) { - // Update node - [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { - UIView *view = viewRegistry[reactTag]; - RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], viewManager); - }]; - } + [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + UIView *view = viewRegistry[reactTag]; + RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], viewManager); + }]; } RCT_EXPORT_METHOD(focus:(NSNumber *)reactTag) @@ -1541,16 +1229,12 @@ RCT_EXPORT_METHOD(zoomToRect:(NSNumber *)reactTag RCT_EXPORT_METHOD(setJSResponder:(NSNumber *)reactTag blockNativeResponder:(__unused BOOL)blockNativeResponder) { - RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; - if (!shadowView) { - RCTLogError(@"Invalid view set to be the JS responder - tag %@", reactTag); - } else if (shadowView.layoutOnly) { - RCTLogError(@"Cannot set JS responder to layout-only view with tag %@ - %@, %@", reactTag, shadowView, shadowView.allProps); - } else { - [self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { - _jsResponder = viewRegistry[reactTag]; - }]; - } + [self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + _jsResponder = viewRegistry[reactTag]; + if (!_jsResponder) { + RCTLogError(@"Invalid view set to be the JS responder - tag %zd", reactTag); + } + }]; } RCT_EXPORT_METHOD(clearJSResponder) @@ -1806,27 +1490,3 @@ static UIView *_jsResponder; } @end - -static void RCTTraverseViewNodes(id view, void (^block)(id)) -{ - if (view.reactTag) block(view); - for (id subview in view.reactSubviews) { - RCTTraverseViewNodes(subview, block); - } -} - -static NSDictionary *RCTPropsMerge(NSDictionary *beforeProps, NSDictionary *newProps) -{ - NSMutableDictionary *afterProps = [NSMutableDictionary dictionaryWithDictionary:beforeProps]; - - // Can't use -addEntriesFromDictionary: because we want to remove keys with NSNull values. - [newProps enumerateKeysAndObjectsUsingBlock:^(id key, id obj, __unused BOOL *stop) { - if (obj == (id)kCFNull) { - [afterProps removeObjectForKey:key]; - } else { - afterProps[key] = obj; - } - }]; - - return afterProps; -} diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h index 38edc6e50..1c44033f6 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -41,12 +41,6 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *viewRegistry); @property (nonatomic, assign) RCTUpdateLifecycle layoutLifecycle; @property (nonatomic, assign) BOOL hasOnLayout; -@property (nonatomic, assign, readonly, getter=isLayoutOnly) BOOL layoutOnly; -@property (nonatomic, copy) NSDictionary *allProps; - -/// `frame` adjusted for recursive superview `layoutOnly` status. -@property (nonatomic, assign, readonly) CGRect adjustedFrame; - /** * isNewView - Used to track the first time the view is introduced into the hierarchy. It is initialized YES, then is * set to NO in RCTUIManager after the layout pass is done and all frames have been extracted to be applied to the diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index e5cccbd3f..9d56bb906 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -367,10 +367,8 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st - (NSString *)description { NSString *description = super.description; - if (self.layoutOnly) { - description = [@"* " stringByAppendingString:description]; - } - return [[description substringToIndex:description.length - 1] stringByAppendingFormat:@"; viewName: %@; reactTag: %@; frame: %@>", self.viewName, self.reactTag, NSStringFromCGRect(self.frame)]; + description = [[description substringToIndex:description.length - 1] stringByAppendingFormat:@"; viewName: %@; reactTag: %@; frame: %@>", self.viewName, self.reactTag, NSStringFromCGRect(self.frame)]; + return description; } - (void)addRecursiveDescriptionToString:(NSMutableString *)string atLevel:(NSUInteger)level @@ -394,94 +392,6 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st return description; } -- (BOOL)isLayoutOnly -{ - if (![self.viewName isEqualToString:@"RCTView"]) { - // For now, only `RCTView`s can be layout-only. - return NO; - } - - // dispatch_once is unnecessary because this property SHOULD only be accessed - // on the shadow queue - static NSSet *layoutKeys; - static NSSet *specialKeys; - if (!layoutKeys || !specialKeys) { - // Taken from LayoutPropTypes.js with the exception that borderWidth, - // borderTopWidth, borderBottomWidth, borderLeftWidth, and borderRightWidth - // were removed because black color is assumed - static NSString *const layoutKeyStrings[] = { - @"width", - @"height", - @"top", - @"left", - @"right", - @"bottom", - @"margin", - @"marginVertical", - @"marginHorizontal", - @"marginTop", - @"marginBottom", - @"marginLeft", - @"marginRight", - @"padding", - @"paddingVertical", - @"paddingHorizontal", - @"paddingTop", - @"paddingBottom", - @"paddingLeft", - @"paddingRight", - @"position", - @"flexDirection", - @"flexWrap", - @"justifyContent", - @"alignItems", - @"alignSelf", - @"flex", - }; - layoutKeys = [NSSet setWithObjects:layoutKeyStrings count:sizeof(layoutKeyStrings)/sizeof(*layoutKeyStrings)]; - // layoutKeys are the only keys whose presence does not reject layout-only status. - - static NSString *const specialKeyStrings[] = { - @"accessible", - @"collapsible", - }; - specialKeys = [NSSet setWithObjects:specialKeyStrings count:sizeof(specialKeyStrings)/sizeof(*specialKeyStrings)]; - // specialKeys are keys whose presence does not indicate whether layout-only or not - // their values must be tested below - } - - NSNumber *collapsible = self.allProps[@"collapsible"]; - if (collapsible && !collapsible.boolValue) { - return NO; - } - - NSNumber *accessible = self.allProps[@"accessible"]; - if (accessible && accessible.boolValue) { - return NO; - } - - for (NSString *key in self.allProps) { - if (![specialKeys containsObject:key] && ![layoutKeys containsObject:key]) { - return NO; - } - } - - return YES; -} - -- (CGRect)adjustedFrame -{ - CGRect frame = self.frame; - RCTShadowView *superview = self; - while ((superview = superview.superview) && superview.layoutOnly) { - const CGRect superviewFrame = superview.frame; - frame.origin.x += superviewFrame.origin.x; - frame.origin.y += superviewFrame.origin.y; - } - - return frame; -} - // Margin #define RCT_MARGIN_PROPERTY(prop, metaProp) \ diff --git a/React/Views/RCTViewNodeProtocol.h b/React/Views/RCTViewNodeProtocol.h index 96eb78f1a..e78cc2ce7 100644 --- a/React/Views/RCTViewNodeProtocol.h +++ b/React/Views/RCTViewNodeProtocol.h @@ -15,11 +15,10 @@ @protocol RCTViewNodeProtocol @property (nonatomic, copy) NSNumber *reactTag; -@property (nonatomic, assign) CGRect frame; - (void)insertReactSubview:(id)subview atIndex:(NSInteger)atIndex; - (void)removeReactSubview:(id)subview; -- (NSArray *)reactSubviews; +- (NSMutableArray *)reactSubviews; - (id)reactSuperview; - (NSNumber *)reactTagAtPoint:(CGPoint)point;