From 66d3f3c6165bdffcfae58d539660bfad1f9ce23e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 7 Jul 2015 07:38:24 -0700 Subject: [PATCH] [rn] Keep native ListView child frames in sync on JS wrapper Summary: At the moment the `ListView.js` `_childFrames` variable is only updated on scroll. As a consequence, `onChangeVisibleRows` won't get triggered for the initial render, nor any future render not trigered by scroll events. To fix this we need to make sure native and JS have the child frames in sync. --- .../CustomComponents/ListView/ListView.js | 17 ++++++-- React/Views/RCTScrollView.m | 40 ++++++++++--------- React/Views/RCTScrollViewManager.m | 23 +++++++++++ 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index cebfac68e994e5..2cf967e5035ef1 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -29,6 +29,7 @@ var ListViewDataSource = require('ListViewDataSource'); var React = require('React'); var RCTUIManager = require('NativeModules').UIManager; +var RKScrollViewManager = require('NativeModules').ScrollViewManager; var ScrollView = require('ScrollView'); var ScrollResponder = require('ScrollResponder'); var StaticRenderer = require('StaticRenderer'); @@ -400,6 +401,13 @@ var ListView = React.createClass({ logError, this._setScrollVisibleHeight ); + + // RKScrollViewManager.calculateChildFrames not available on every platform + RKScrollViewManager && RKScrollViewManager.calculateChildFrames && + RKScrollViewManager.calculateChildFrames( + React.findNodeHandle(this.refs[SCROLLVIEW_REF]), + this._updateChildFrames, + ); }, _setScrollContentHeight: function(left, top, width, height) { @@ -412,6 +420,10 @@ var ListView = React.createClass({ this._renderMoreRowsIfNeeded(); }, + _updateChildFrames: function(childFrames) { + this._updateVisibleRows(childFrames); + }, + _renderMoreRowsIfNeeded: function() { if (this.scrollProperties.contentHeight === null || this.scrollProperties.visibleHeight === null || @@ -449,11 +461,10 @@ var ListView = React.createClass({ scrollProperties.offsetY; }, - _updateVisibleRows: function(e) { + _updateVisibleRows: function(updatedFrames) { if (!this.props.onChangeVisibleRows) { return; // No need to compute visible rows if there is no callback } - var updatedFrames = e && e.nativeEvent.updatedChildFrames; if (updatedFrames) { updatedFrames.forEach((newFrame) => { this._childFrames[newFrame.index] = merge(newFrame); @@ -522,7 +533,7 @@ var ListView = React.createClass({ this.scrollProperties.visibleHeight = e.nativeEvent.layoutMeasurement.height; this.scrollProperties.contentHeight = e.nativeEvent.contentSize.height; this.scrollProperties.offsetY = e.nativeEvent.contentOffset.y; - this._updateVisibleRows(e); + this._updateVisibleRows(e.nativeEvent.updatedChildFrames); var nearEnd = this._getDistanceFromEnd(this.scrollProperties) < this.props.onEndReachedThreshold; if (nearEnd && this.props.onEndReached && diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index 2528678493d56a..bc4db361ee9895 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -357,6 +357,10 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event @end +@interface RCTScrollView (Private) +- (NSArray *)calculateChildFramesData; +@end + @implementation RCTScrollView { RCTEventDispatcher *_eventDispatcher; @@ -533,6 +537,23 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView (_scrollEventThrottle > 0 && _scrollEventThrottle < (now - _lastScrollDispatchTime))) { // Calculate changed frames + NSArray *childFrames = [self calculateChildFramesData]; + + // Dispatch event + [_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeMove + reactTag:self.reactTag + scrollView:scrollView + userData:@{@"updatedChildFrames": childFrames}]; + + // Update dispatch time + _lastScrollDispatchTime = now; + _allowNextScrollNoMatterWhat = NO; + } + RCT_FORWARD_SCROLL_EVENT(scrollViewDidScroll:scrollView); +} + +- (NSArray *)calculateChildFramesData +{ NSMutableArray *updatedChildFrames = [[NSMutableArray alloc] init]; [[_contentView reactSubviews] enumerateObjectsUsingBlock: ^(UIView *subview, NSUInteger idx, __unused BOOL *stop) { @@ -558,26 +579,9 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView @"height": @(newFrame.size.height), }]; } - }]; - // If there are new frames, add them to event data - NSDictionary *userData = nil; - if (updatedChildFrames.count > 0) { - userData = @{@"updatedChildFrames": updatedChildFrames}; - } - - // Dispatch event - [_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeMove - reactTag:self.reactTag - scrollView:scrollView - userData:userData]; - - // Update dispatch time - _lastScrollDispatchTime = now; - _allowNextScrollNoMatterWhat = NO; - } - RCT_FORWARD_SCROLL_EVENT(scrollViewDidScroll:scrollView); + return updatedChildFrames; } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m index 6e74d13332d7e6..a83bf61dae108c 100644 --- a/React/Views/RCTScrollViewManager.m +++ b/React/Views/RCTScrollViewManager.m @@ -14,6 +14,10 @@ #import "RCTSparseArray.h" #import "RCTUIManager.h" +@interface RCTScrollView (Private) +- (NSArray *)calculateChildFramesData; +@end + @implementation RCTConvert (UIScrollView) RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{ @@ -91,4 +95,23 @@ - (NSDictionary *)constantsToExport }]; } +RCT_EXPORT_METHOD(calculateChildFrames:(NSNumber *)reactTag + callback:(RCTResponseSenderBlock)callback) +{ + [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + + UIView *view = viewRegistry[reactTag]; + if (!view) { + RCTLogError(@"Cannot find view with tag #%@", reactTag); + return; + } + + NSArray *childFrames = [((RCTScrollView *)view) calculateChildFramesData]; + + if (childFrames) { + callback(@[childFrames]); + } + }]; +} + @end